mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 02:07:25 +01:00
Starting rework.
This commit is contained in:
parent
0fc8992271
commit
7af38aa2ce
58 changed files with 8857 additions and 4923 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
|||
bin/
|
||||
obj/
|
||||
.vs/
|
||||
Glamourer.json
|
||||
Glamourer.json
|
||||
private/
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -1,4 +0,0 @@
|
|||
[submodule "OtterGui"]
|
||||
path = OtterGui
|
||||
url = git@github.com:Ottermandias/OtterGui.git
|
||||
branch = main
|
||||
159
Glamourer.GameData/CharacterCustomization.cs
Normal file
159
Glamourer.GameData/CharacterCustomization.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public readonly unsafe struct CharacterCustomization
|
||||
{
|
||||
public static readonly CharacterCustomization Null = new(null);
|
||||
|
||||
private readonly CustomizationData* _data;
|
||||
|
||||
public IntPtr Address
|
||||
=> (IntPtr)_data;
|
||||
|
||||
public CharacterCustomization(CustomizationData* data)
|
||||
=> _data = data;
|
||||
|
||||
public ref Race Race
|
||||
=> ref _data->Race;
|
||||
|
||||
public ref SubRace Clan
|
||||
=> ref _data->Clan;
|
||||
|
||||
public Gender Gender
|
||||
{
|
||||
get => _data->Gender;
|
||||
set => _data->Gender = value;
|
||||
}
|
||||
|
||||
public ref byte BodyType
|
||||
=> ref _data->BodyType;
|
||||
|
||||
public ref byte Height
|
||||
=> ref _data->Height;
|
||||
|
||||
public ref byte Face
|
||||
=> ref _data->Face;
|
||||
|
||||
public ref byte Hairstyle
|
||||
=> ref _data->Hairstyle;
|
||||
|
||||
public bool HighlightsOn
|
||||
{
|
||||
get => _data->HighlightsOn;
|
||||
set => _data->HighlightsOn = value;
|
||||
}
|
||||
|
||||
public ref byte SkinColor
|
||||
=> ref _data->SkinColor;
|
||||
|
||||
public ref byte EyeColorRight
|
||||
=> ref _data->EyeColorRight;
|
||||
|
||||
public ref byte HairColor
|
||||
=> ref _data->HairColor;
|
||||
|
||||
public ref byte HighlightsColor
|
||||
=> ref _data->HighlightsColor;
|
||||
|
||||
public ref byte FacialFeatures
|
||||
=> ref _data->FacialFeatures;
|
||||
|
||||
public ref byte TattooColor
|
||||
=> ref _data->TattooColor;
|
||||
|
||||
public ref byte Eyebrow
|
||||
=> ref _data->Eyebrow;
|
||||
|
||||
public ref byte EyeColorLeft
|
||||
=> ref _data->EyeColorLeft;
|
||||
|
||||
public byte EyeShape
|
||||
{
|
||||
get => _data->EyeShape;
|
||||
set => _data->EyeShape = value;
|
||||
}
|
||||
|
||||
public byte FacePaint
|
||||
{
|
||||
get => _data->FacePaint;
|
||||
set => _data->FacePaint = value;
|
||||
}
|
||||
|
||||
public bool FacePaintReversed
|
||||
{
|
||||
get => _data->FacePaintReversed;
|
||||
set => _data->FacePaintReversed = value;
|
||||
}
|
||||
|
||||
public byte Mouth
|
||||
{
|
||||
get => _data->Mouth;
|
||||
set => _data->Mouth = value;
|
||||
}
|
||||
|
||||
public bool SmallIris
|
||||
{
|
||||
get => _data->SmallIris;
|
||||
set => _data->SmallIris = value;
|
||||
}
|
||||
|
||||
public bool Lipstick
|
||||
{
|
||||
get => _data->Lipstick;
|
||||
set => _data->Lipstick = value;
|
||||
}
|
||||
|
||||
public ref byte Nose
|
||||
=> ref _data->Nose;
|
||||
|
||||
public ref byte Jaw
|
||||
=> ref _data->Jaw;
|
||||
|
||||
public ref byte LipColor
|
||||
=> ref _data->LipColor;
|
||||
|
||||
public ref byte MuscleMass
|
||||
=> ref _data->MuscleMass;
|
||||
|
||||
public ref byte TailShape
|
||||
=> ref _data->TailShape;
|
||||
|
||||
public ref byte BustSize
|
||||
=> ref _data->BustSize;
|
||||
|
||||
public ref byte FacePaintColor
|
||||
=> ref _data->FacePaintColor;
|
||||
|
||||
public bool FacialFeature(int idx)
|
||||
=> _data->FacialFeature(idx);
|
||||
|
||||
public void FacialFeature(int idx, bool set)
|
||||
=> _data->FacialFeature(idx, set);
|
||||
|
||||
public byte this[CustomizationId id]
|
||||
{
|
||||
get => _data->Get(id);
|
||||
set => _data->Set(id, value);
|
||||
}
|
||||
|
||||
public static implicit operator CharacterCustomization(CustomizationData* val)
|
||||
=> new(val);
|
||||
|
||||
public static implicit operator CharacterCustomization(IntPtr val)
|
||||
=> new((CustomizationData*)val);
|
||||
|
||||
public static implicit operator bool(CharacterCustomization customize)
|
||||
=> customize._data != null;
|
||||
|
||||
public static bool operator true(CharacterCustomization customize)
|
||||
=> customize._data != null;
|
||||
|
||||
public static bool operator false(CharacterCustomization customize)
|
||||
=> customize._data == null;
|
||||
|
||||
public static bool operator !(CharacterCustomization customize)
|
||||
=> customize._data == null;
|
||||
}
|
||||
105
Glamourer.GameData/CharacterEquip.cs
Normal file
105
Glamourer.GameData/CharacterEquip.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public readonly unsafe struct CharacterEquip
|
||||
{
|
||||
public static readonly CharacterEquip Null = new(null);
|
||||
|
||||
private readonly CharacterArmor* _armor;
|
||||
|
||||
public IntPtr Address
|
||||
=> (IntPtr)_armor;
|
||||
|
||||
public ref CharacterArmor this[int idx]
|
||||
=> ref _armor[idx];
|
||||
|
||||
public ref CharacterArmor this[uint idx]
|
||||
=> ref _armor[idx];
|
||||
|
||||
public ref CharacterArmor this[EquipSlot slot]
|
||||
=> ref _armor[IndexOf(slot)];
|
||||
|
||||
|
||||
public ref CharacterArmor Head
|
||||
=> ref _armor[0];
|
||||
|
||||
public ref CharacterArmor Body
|
||||
=> ref _armor[1];
|
||||
|
||||
public ref CharacterArmor Hands
|
||||
=> ref _armor[2];
|
||||
|
||||
public ref CharacterArmor Legs
|
||||
=> ref _armor[3];
|
||||
|
||||
public ref CharacterArmor Feet
|
||||
=> ref _armor[4];
|
||||
|
||||
public ref CharacterArmor Ears
|
||||
=> ref _armor[5];
|
||||
|
||||
public ref CharacterArmor Neck
|
||||
=> ref _armor[6];
|
||||
|
||||
public ref CharacterArmor Wrists
|
||||
=> ref _armor[7];
|
||||
|
||||
public ref CharacterArmor RFinger
|
||||
=> ref _armor[8];
|
||||
|
||||
public ref CharacterArmor LFinger
|
||||
=> ref _armor[9];
|
||||
|
||||
public CharacterEquip(CharacterArmor* val)
|
||||
=> _armor = val;
|
||||
|
||||
public static implicit operator CharacterEquip(CharacterArmor* val)
|
||||
=> new(val);
|
||||
|
||||
public static implicit operator CharacterEquip(IntPtr val)
|
||||
=> new((CharacterArmor*)val);
|
||||
|
||||
public static implicit operator CharacterEquip(ReadOnlySpan<CharacterArmor> val)
|
||||
{
|
||||
if (val.Length != 10)
|
||||
throw new ArgumentException("Invalid number of equipment pieces in span.");
|
||||
|
||||
fixed (CharacterArmor* ptr = val)
|
||||
{
|
||||
return new CharacterEquip(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator bool(CharacterEquip equip)
|
||||
=> equip._armor != null;
|
||||
|
||||
public static bool operator true(CharacterEquip equip)
|
||||
=> equip._armor != null;
|
||||
|
||||
public static bool operator false(CharacterEquip equip)
|
||||
=> equip._armor == null;
|
||||
|
||||
public static bool operator !(CharacterEquip equip)
|
||||
=> equip._armor == null;
|
||||
|
||||
private static int IndexOf(EquipSlot slot)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.Head => 0,
|
||||
EquipSlot.Body => 1,
|
||||
EquipSlot.Hands => 2,
|
||||
EquipSlot.Legs => 3,
|
||||
EquipSlot.Feet => 4,
|
||||
EquipSlot.Ears => 5,
|
||||
EquipSlot.Neck => 6,
|
||||
EquipSlot.Wrists => 7,
|
||||
EquipSlot.RFinger => 8,
|
||||
EquipSlot.LFinger => 9,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(slot), slot, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Lumina.Excel.GeneratedSheets;
|
|||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
// A custom version of CharaMakeParams that is easier to parse.
|
||||
[Sheet("CharaMakeParams")]
|
||||
public class CharaMakeParams : ExcelRow
|
||||
{
|
||||
|
|
@ -43,10 +44,9 @@ public class CharaMakeParams : ExcelRow
|
|||
public uint[] Icons;
|
||||
}
|
||||
|
||||
public LazyRow<Race> Race { get; set; } = null!;
|
||||
public LazyRow<Tribe> Tribe { get; set; } = null!;
|
||||
|
||||
public sbyte Gender { get; set; }
|
||||
public LazyRow<Race> Race { get; set; } = null!;
|
||||
public LazyRow<Tribe> Tribe { get; set; } = null!;
|
||||
public sbyte Gender { get; set; }
|
||||
|
||||
public Menu[] Menus { get; set; } = new Menu[NumMenus];
|
||||
public byte[] Voices { get; set; } = new byte[NumVoices];
|
||||
|
|
@ -60,15 +60,17 @@ public class CharaMakeParams : ExcelRow
|
|||
Race = new LazyRow<Race>(gameData, parser.ReadColumn<uint>(0), language);
|
||||
Tribe = new LazyRow<Tribe>(gameData, parser.ReadColumn<uint>(1), language);
|
||||
Gender = parser.ReadColumn<sbyte>(2);
|
||||
var currentOffset = 0;
|
||||
for (var i = 0; i < NumMenus; ++i)
|
||||
{
|
||||
Menus[i].Id = parser.ReadColumn<uint>(3 + 0 * NumMenus + i);
|
||||
Menus[i].InitVal = parser.ReadColumn<byte>(3 + 1 * NumMenus + i);
|
||||
Menus[i].Type = (MenuType)parser.ReadColumn<byte>(3 + 2 * NumMenus + i);
|
||||
Menus[i].Size = parser.ReadColumn<byte>(3 + 3 * NumMenus + i);
|
||||
Menus[i].LookAt = parser.ReadColumn<byte>(3 + 4 * NumMenus + i);
|
||||
Menus[i].Mask = parser.ReadColumn<uint>(3 + 5 * NumMenus + i);
|
||||
Menus[i].Customization = (CustomizationId)parser.ReadColumn<uint>(3 + 6 * NumMenus + i);
|
||||
currentOffset = 3 + i;
|
||||
Menus[i].Id = parser.ReadColumn<uint>(0 * NumMenus + currentOffset);
|
||||
Menus[i].InitVal = parser.ReadColumn<byte>(1 * NumMenus + currentOffset);
|
||||
Menus[i].Type = (MenuType)parser.ReadColumn<byte>(2 * NumMenus + currentOffset);
|
||||
Menus[i].Size = parser.ReadColumn<byte>(3 * NumMenus + currentOffset);
|
||||
Menus[i].LookAt = parser.ReadColumn<byte>(4 * NumMenus + currentOffset);
|
||||
Menus[i].Mask = parser.ReadColumn<uint>(5 * NumMenus + currentOffset);
|
||||
Menus[i].Customization = (CustomizationId)parser.ReadColumn<uint>(6 * NumMenus + currentOffset);
|
||||
Menus[i].Values = new uint[Menus[i].Size];
|
||||
|
||||
switch (Menus[i].Type)
|
||||
|
|
@ -78,47 +80,42 @@ public class CharaMakeParams : ExcelRow
|
|||
case MenuType.Percentage:
|
||||
break;
|
||||
default:
|
||||
currentOffset += 7 * NumMenus;
|
||||
for (var j = 0; j < Menus[i].Size; ++j)
|
||||
Menus[i].Values[j] = parser.ReadColumn<uint>(3 + (7 + j) * NumMenus + i);
|
||||
Menus[i].Values[j] = parser.ReadColumn<uint>(j * NumMenus + currentOffset);
|
||||
break;
|
||||
}
|
||||
|
||||
Menus[i].Graphic = new byte[NumGraphics];
|
||||
currentOffset = 3 + (MaxNumValues + 7) * NumMenus + i;
|
||||
for (var j = 0; j < NumGraphics; ++j)
|
||||
Menus[i].Graphic[j] = parser.ReadColumn<byte>(3 + (MaxNumValues + 7 + j) * NumMenus + i);
|
||||
Menus[i].Graphic[j] = parser.ReadColumn<byte>(j * NumMenus + currentOffset);
|
||||
}
|
||||
|
||||
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus;
|
||||
for (var i = 0; i < NumVoices; ++i)
|
||||
Voices[i] = parser.ReadColumn<byte>(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + i);
|
||||
Voices[i] = parser.ReadColumn<byte>(currentOffset++);
|
||||
|
||||
for (var i = 0; i < NumFaces; ++i)
|
||||
{
|
||||
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + i;
|
||||
FacialFeatureByFace[i].Icons = new uint[NumFeatures];
|
||||
for (var j = 0; j < NumFeatures; ++j)
|
||||
{
|
||||
FacialFeatureByFace[i].Icons[j] =
|
||||
(uint)parser.ReadColumn<int>(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + j * NumFaces + i);
|
||||
}
|
||||
FacialFeatureByFace[i].Icons[j] = (uint)parser.ReadColumn<int>(j * NumFaces + currentOffset);
|
||||
}
|
||||
|
||||
for (var i = 0; i < NumEquip; ++i)
|
||||
{
|
||||
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7;
|
||||
Equip[i] = new CharaMakeType.UnkData3347Obj()
|
||||
{
|
||||
Helmet = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 0),
|
||||
Top = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 1),
|
||||
Gloves = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 2),
|
||||
Legs = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 3),
|
||||
Shoes = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 4),
|
||||
Weapon = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 5),
|
||||
SubWeapon = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 6),
|
||||
Helmet = parser.ReadColumn<ulong>(currentOffset + 0),
|
||||
Top = parser.ReadColumn<ulong>(currentOffset + 1),
|
||||
Gloves = parser.ReadColumn<ulong>(currentOffset + 2),
|
||||
Legs = parser.ReadColumn<ulong>(currentOffset + 3),
|
||||
Shoes = parser.ReadColumn<ulong>(currentOffset + 4),
|
||||
Weapon = parser.ReadColumn<ulong>(currentOffset + 5),
|
||||
SubWeapon = parser.ReadColumn<ulong>(currentOffset + 6),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,46 @@
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Logging;
|
||||
|
||||
namespace Glamourer;
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public class CmpFile
|
||||
// Convert the Human.Cmp file into color sets.
|
||||
// If the file can not be read due to TexTools corruption, create a 0-array of size MinSize.
|
||||
internal class CmpFile
|
||||
{
|
||||
public readonly Lumina.Data.FileResource File;
|
||||
public readonly uint[] RgbaColors;
|
||||
private readonly Lumina.Data.FileResource? _file;
|
||||
private readonly uint[] _rgbaColors;
|
||||
|
||||
// No error checking since only called internally.
|
||||
public IEnumerable<uint> GetSlice(int offset, int count)
|
||||
=> _rgbaColors.Length >= offset + count ? _rgbaColors.Skip(offset).Take(count) : Enumerable.Repeat(0u, count);
|
||||
|
||||
public bool Valid
|
||||
=> _file != null;
|
||||
|
||||
public CmpFile(DataManager gameData)
|
||||
{
|
||||
File = gameData.GetFile("chara/xls/charamake/human.cmp")!;
|
||||
RgbaColors = new uint[File.Data.Length >> 2];
|
||||
for (var i = 0; i < File.Data.Length; i += 4)
|
||||
try
|
||||
{
|
||||
RgbaColors[i >> 2] = File.Data[i]
|
||||
| (uint)(File.Data[i + 1] << 8)
|
||||
| (uint)(File.Data[i + 2] << 16)
|
||||
| (uint)(File.Data[i + 3] << 24);
|
||||
_file = gameData.GetFile("chara/xls/charamake/human.cmp")!;
|
||||
_rgbaColors = new uint[_file.Data.Length >> 2];
|
||||
for (var i = 0; i < _file.Data.Length; i += 4)
|
||||
{
|
||||
_rgbaColors[i >> 2] = _file.Data[i]
|
||||
| (uint)(_file.Data[i + 1] << 8)
|
||||
| (uint)(_file.Data[i + 2] << 16)
|
||||
| (uint)(_file.Data[i + 3] << 24);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n"
|
||||
+ "======== This usually indicates an error with your index files caused by TexTools modifications.\n"
|
||||
+ "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e);
|
||||
_file = null;
|
||||
_rgbaColors = Array.Empty<uint>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,45 @@
|
|||
namespace Glamourer.Customization
|
||||
{
|
||||
public enum CustomName
|
||||
{
|
||||
Clan = 0,
|
||||
Gender,
|
||||
Reverse,
|
||||
OddEyes,
|
||||
IrisSmall,
|
||||
IrisLarge,
|
||||
IrisSize,
|
||||
MidlanderM,
|
||||
HighlanderM,
|
||||
WildwoodM,
|
||||
DuskwightM,
|
||||
PlainsfolkM,
|
||||
DunesfolkM,
|
||||
SeekerOfTheSunM,
|
||||
KeeperOfTheMoonM,
|
||||
SeawolfM,
|
||||
HellsguardM,
|
||||
RaenM,
|
||||
XaelaM,
|
||||
HelionM,
|
||||
LostM,
|
||||
RavaM,
|
||||
VeenaM,
|
||||
MidlanderF,
|
||||
HighlanderF,
|
||||
WildwoodF,
|
||||
DuskwightF,
|
||||
PlainsfolkF,
|
||||
DunesfolkF,
|
||||
SeekerOfTheSunF,
|
||||
KeeperOfTheMoonF,
|
||||
SeawolfF,
|
||||
HellsguardF,
|
||||
RaenF,
|
||||
XaelaF,
|
||||
HelionF,
|
||||
LostF,
|
||||
RavaF,
|
||||
VeenaF,
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
Num,
|
||||
}
|
||||
// Localization from the game files directly.
|
||||
public enum CustomName
|
||||
{
|
||||
Clan = 0,
|
||||
Gender,
|
||||
Reverse,
|
||||
OddEyes,
|
||||
IrisSmall,
|
||||
IrisLarge,
|
||||
IrisSize,
|
||||
MidlanderM,
|
||||
HighlanderM,
|
||||
WildwoodM,
|
||||
DuskwightM,
|
||||
PlainsfolkM,
|
||||
DunesfolkM,
|
||||
SeekerOfTheSunM,
|
||||
KeeperOfTheMoonM,
|
||||
SeawolfM,
|
||||
HellsguardM,
|
||||
RaenM,
|
||||
XaelaM,
|
||||
HelionM,
|
||||
LostM,
|
||||
RavaM,
|
||||
VeenaM,
|
||||
MidlanderF,
|
||||
HighlanderF,
|
||||
WildwoodF,
|
||||
DuskwightF,
|
||||
PlainsfolkF,
|
||||
DunesfolkF,
|
||||
SeekerOfTheSunF,
|
||||
KeeperOfTheMoonF,
|
||||
SeawolfF,
|
||||
HellsguardF,
|
||||
RaenF,
|
||||
XaelaF,
|
||||
HelionF,
|
||||
LostF,
|
||||
RavaF,
|
||||
VeenaF,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,33 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
// Any customization value can be represented in 8 bytes by its ID,
|
||||
// a byte value, an optional value-id and an optional icon or color.
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct Customization
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct Customization
|
||||
[FieldOffset(0)]
|
||||
public readonly CustomizationId Id;
|
||||
|
||||
[FieldOffset(1)]
|
||||
public readonly byte Value;
|
||||
|
||||
[FieldOffset(2)]
|
||||
public readonly ushort CustomizeId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint IconId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint Color;
|
||||
|
||||
public Customization(CustomizationId id, byte value, uint data = 0, ushort customizeId = 0)
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public readonly CustomizationId Id;
|
||||
|
||||
[FieldOffset(1)]
|
||||
public readonly byte Value;
|
||||
|
||||
[FieldOffset(2)]
|
||||
public readonly ushort CustomizeId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint IconId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint Color;
|
||||
|
||||
public Customization(CustomizationId id, byte value, uint data = 0, ushort customizeId = 0)
|
||||
{
|
||||
Id = id;
|
||||
Value = value;
|
||||
IconId = data;
|
||||
Color = data;
|
||||
CustomizeId = customizeId;
|
||||
}
|
||||
Id = id;
|
||||
Value = value;
|
||||
IconId = data;
|
||||
Color = data;
|
||||
CustomizeId = customizeId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,59 +2,17 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public unsafe struct LazyCustomization
|
||||
{
|
||||
public CharacterCustomization* Address;
|
||||
|
||||
public LazyCustomization(IntPtr characterPtr)
|
||||
=> Address = (CharacterCustomization*)(characterPtr + CharacterCustomization.CustomizationOffset);
|
||||
|
||||
public ref CharacterCustomization Value
|
||||
=> ref *Address;
|
||||
|
||||
public LazyCustomization(CharacterCustomization data)
|
||||
=> Address = &data;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct CharacterCustomization
|
||||
public struct CustomizationData
|
||||
{
|
||||
public const int CustomizationOffset = 0x830;
|
||||
public const int CustomizationBytes = 26;
|
||||
|
||||
public static CharacterCustomization Default = new()
|
||||
{
|
||||
Race = Race.Hyur,
|
||||
Gender = Gender.Male,
|
||||
BodyType = 1,
|
||||
Height = 50,
|
||||
Clan = SubRace.Midlander,
|
||||
Face = 1,
|
||||
Hairstyle = 1,
|
||||
HighlightsOn = false,
|
||||
SkinColor = 1,
|
||||
EyeColorRight = 1,
|
||||
HighlightsColor = 1,
|
||||
FacialFeatures = 0,
|
||||
TattooColor = 1,
|
||||
Eyebrow = 1,
|
||||
EyeColorLeft = 1,
|
||||
EyeShape = 1,
|
||||
Nose = 1,
|
||||
Jaw = 1,
|
||||
Mouth = 1,
|
||||
LipColor = 1,
|
||||
MuscleMass = 50,
|
||||
TailShape = 1,
|
||||
BustSize = 50,
|
||||
FacePaint = 1,
|
||||
FacePaintColor = 1,
|
||||
};
|
||||
|
||||
public Race Race;
|
||||
private byte _gender;
|
||||
public byte BodyType;
|
||||
|
|
@ -82,21 +40,25 @@ public struct CharacterCustomization
|
|||
private byte _facePaint;
|
||||
public byte FacePaintColor;
|
||||
|
||||
// Skip Unknown Gender
|
||||
public Gender Gender
|
||||
{
|
||||
get => (Gender)(_gender + 1);
|
||||
set => _gender = (byte)(value - 1);
|
||||
}
|
||||
|
||||
// Single bit flag.
|
||||
public bool HighlightsOn
|
||||
{
|
||||
get => (_highlightsOn & 128) == 128;
|
||||
set => _highlightsOn = (byte)(value ? _highlightsOn | 128 : _highlightsOn & 127);
|
||||
}
|
||||
|
||||
// Get status of specific facial feature 0-7.
|
||||
public bool FacialFeature(int idx)
|
||||
=> (FacialFeatures & (1 << idx)) != 0;
|
||||
|
||||
// Set value of specific facial feature 0-7.
|
||||
public void FacialFeature(int idx, bool set)
|
||||
{
|
||||
if (set)
|
||||
|
|
@ -105,66 +67,108 @@ public struct CharacterCustomization
|
|||
FacialFeatures &= (byte)~(1 << idx);
|
||||
}
|
||||
|
||||
// Lower 7 bits
|
||||
public byte EyeShape
|
||||
{
|
||||
get => (byte)(_eyeShape & 127);
|
||||
set => _eyeShape = (byte)((value & 127) | (_eyeShape & 128));
|
||||
}
|
||||
|
||||
// Uppermost bit flag.
|
||||
public bool SmallIris
|
||||
{
|
||||
get => (_eyeShape & 128) == 128;
|
||||
set => _eyeShape = (byte)(value ? _eyeShape | 128 : _eyeShape & 127);
|
||||
}
|
||||
|
||||
|
||||
// Lower 7 bits.
|
||||
public byte Mouth
|
||||
{
|
||||
get => (byte)(_mouth & 127);
|
||||
set => _mouth = (byte)((value & 127) | (_mouth & 128));
|
||||
}
|
||||
|
||||
// Uppermost bit flag.
|
||||
public bool Lipstick
|
||||
{
|
||||
get => (_mouth & 128) == 128;
|
||||
set => _mouth = (byte)(value ? _mouth | 128 : _mouth & 127);
|
||||
}
|
||||
|
||||
// Lower 7 bits.
|
||||
public byte FacePaint
|
||||
{
|
||||
get => (byte)(_facePaint & 127);
|
||||
set => _facePaint = (byte)((value & 127) | (_facePaint & 128));
|
||||
}
|
||||
|
||||
// Uppermost bit flag.
|
||||
public bool FacePaintReversed
|
||||
{
|
||||
get => (_facePaint & 128) == 128;
|
||||
set => _facePaint = (byte)(value ? _facePaint | 128 : _facePaint & 127);
|
||||
}
|
||||
|
||||
public unsafe void Read(IntPtr customizeAddress)
|
||||
public static CustomizationData Default = new()
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
Race = Race.Hyur,
|
||||
Gender = Gender.Male,
|
||||
BodyType = 1,
|
||||
Height = 50,
|
||||
Clan = SubRace.Midlander,
|
||||
Face = 1,
|
||||
Hairstyle = 1,
|
||||
HighlightsOn = false,
|
||||
SkinColor = 1,
|
||||
EyeColorRight = 1,
|
||||
HighlightsColor = 1,
|
||||
FacialFeatures = 0,
|
||||
TattooColor = 1,
|
||||
Eyebrow = 1,
|
||||
EyeColorLeft = 1,
|
||||
EyeShape = 1,
|
||||
Nose = 1,
|
||||
Jaw = 1,
|
||||
Mouth = 1,
|
||||
LipColor = 1,
|
||||
MuscleMass = 50,
|
||||
TailShape = 1,
|
||||
BustSize = 50,
|
||||
FacePaint = 1,
|
||||
FacePaintColor = 1,
|
||||
};
|
||||
|
||||
public unsafe void Read(CustomizationData* customize)
|
||||
{
|
||||
fixed (CustomizationData* ptr = &this)
|
||||
{
|
||||
Buffer.MemoryCopy(customizeAddress.ToPointer(), ptr, CustomizationBytes, CustomizationBytes);
|
||||
*ptr = *customize;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Read(Customization* customize)
|
||||
=> Read((IntPtr)customize);
|
||||
public unsafe void Read(IntPtr customizeAddress)
|
||||
=> Read((CustomizationData*)customizeAddress);
|
||||
|
||||
public void Read(Character character)
|
||||
=> Read(character.Address + CustomizationOffset);
|
||||
|
||||
public CharacterCustomization(Character character)
|
||||
public unsafe void Read(Human* human)
|
||||
=> Read((CustomizationData*)human->CustomizeData);
|
||||
|
||||
public CustomizationData(Character character)
|
||||
: this()
|
||||
{
|
||||
Read(character.Address + CustomizationOffset);
|
||||
}
|
||||
|
||||
public byte this[CustomizationId id]
|
||||
public unsafe CustomizationData(Human* human)
|
||||
: this()
|
||||
{
|
||||
get => id switch
|
||||
Read(human);
|
||||
}
|
||||
|
||||
public byte Get(CustomizationId id)
|
||||
=> id switch
|
||||
{
|
||||
CustomizationId.Race => (byte)Race,
|
||||
CustomizationId.Gender => (byte)Gender,
|
||||
|
|
@ -194,100 +198,59 @@ public struct CharacterCustomization
|
|||
CustomizationId.FacePaintColor => FacePaintColor,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
||||
};
|
||||
set
|
||||
|
||||
public void Set(CustomizationId id, byte value)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case CustomizationId.Race:
|
||||
Race = (Race)value;
|
||||
break;
|
||||
case CustomizationId.Gender:
|
||||
Gender = (Gender)value;
|
||||
break;
|
||||
case CustomizationId.BodyType:
|
||||
BodyType = value;
|
||||
break;
|
||||
case CustomizationId.Height:
|
||||
Height = value;
|
||||
break;
|
||||
case CustomizationId.Clan:
|
||||
Clan = (SubRace)value;
|
||||
break;
|
||||
case CustomizationId.Face:
|
||||
Face = value;
|
||||
break;
|
||||
case CustomizationId.Hairstyle:
|
||||
Hairstyle = value;
|
||||
break;
|
||||
case CustomizationId.HighlightsOnFlag:
|
||||
HighlightsOn = (value & 128) == 128;
|
||||
break;
|
||||
case CustomizationId.SkinColor:
|
||||
SkinColor = value;
|
||||
break;
|
||||
case CustomizationId.EyeColorR:
|
||||
EyeColorRight = value;
|
||||
break;
|
||||
case CustomizationId.HairColor:
|
||||
HairColor = value;
|
||||
break;
|
||||
case CustomizationId.HighlightColor:
|
||||
HighlightsColor = value;
|
||||
break;
|
||||
case CustomizationId.FacialFeaturesTattoos:
|
||||
FacialFeatures = value;
|
||||
break;
|
||||
case CustomizationId.TattooColor:
|
||||
TattooColor = value;
|
||||
break;
|
||||
case CustomizationId.Eyebrows:
|
||||
Eyebrow = value;
|
||||
break;
|
||||
case CustomizationId.EyeColorL:
|
||||
EyeColorLeft = value;
|
||||
break;
|
||||
case CustomizationId.EyeShape:
|
||||
EyeShape = value;
|
||||
break;
|
||||
case CustomizationId.Nose:
|
||||
Nose = value;
|
||||
break;
|
||||
case CustomizationId.Jaw:
|
||||
Jaw = value;
|
||||
break;
|
||||
case CustomizationId.Mouth:
|
||||
Mouth = value;
|
||||
break;
|
||||
case CustomizationId.LipColor:
|
||||
LipColor = value;
|
||||
break;
|
||||
case CustomizationId.MuscleToneOrTailEarLength:
|
||||
MuscleMass = value;
|
||||
break;
|
||||
case CustomizationId.TailEarShape:
|
||||
TailShape = value;
|
||||
break;
|
||||
case CustomizationId.BustSize:
|
||||
BustSize = value;
|
||||
break;
|
||||
case CustomizationId.FacePaint:
|
||||
FacePaint = value;
|
||||
break;
|
||||
case CustomizationId.FacePaintColor:
|
||||
FacePaintColor = value;
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(id), id, null);
|
||||
}
|
||||
// @formatter:off
|
||||
case CustomizationId.Race: Race = (Race)value; break;
|
||||
case CustomizationId.Gender: Gender = (Gender)value; break;
|
||||
case CustomizationId.BodyType: BodyType = value; break;
|
||||
case CustomizationId.Height: Height = value; break;
|
||||
case CustomizationId.Clan: Clan = (SubRace)value; break;
|
||||
case CustomizationId.Face: Face = value; break;
|
||||
case CustomizationId.Hairstyle: Hairstyle = value; break;
|
||||
case CustomizationId.HighlightsOnFlag: HighlightsOn = (value & 128) == 128; break;
|
||||
case CustomizationId.SkinColor: SkinColor = value; break;
|
||||
case CustomizationId.EyeColorR: EyeColorRight = value; break;
|
||||
case CustomizationId.HairColor: HairColor = value; break;
|
||||
case CustomizationId.HighlightColor: HighlightsColor = value; break;
|
||||
case CustomizationId.FacialFeaturesTattoos: FacialFeatures = value; break;
|
||||
case CustomizationId.TattooColor: TattooColor = value; break;
|
||||
case CustomizationId.Eyebrows: Eyebrow = value; break;
|
||||
case CustomizationId.EyeColorL: EyeColorLeft = value; break;
|
||||
case CustomizationId.EyeShape: EyeShape = value; break;
|
||||
case CustomizationId.Nose: Nose = value; break;
|
||||
case CustomizationId.Jaw: Jaw = value; break;
|
||||
case CustomizationId.Mouth: Mouth = value; break;
|
||||
case CustomizationId.LipColor: LipColor = value; break;
|
||||
case CustomizationId.MuscleToneOrTailEarLength: MuscleMass = value; break;
|
||||
case CustomizationId.TailEarShape: TailShape = value; break;
|
||||
case CustomizationId.BustSize: BustSize = value; break;
|
||||
case CustomizationId.FacePaint: FacePaint = value; break;
|
||||
case CustomizationId.FacePaintColor: FacePaintColor = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(id), id, null);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
public byte this[CustomizationId id]
|
||||
{
|
||||
get => Get(id);
|
||||
set => Set(id, value);
|
||||
}
|
||||
|
||||
public unsafe void Write(FFXIVClientStructs.FFXIV.Client.Game.Character.Character* character)
|
||||
{
|
||||
fixed (CustomizationData* ptr = &this)
|
||||
{
|
||||
Buffer.MemoryCopy(ptr, character->CustomizeData, CustomizationBytes, CustomizationBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Write(IntPtr characterAddress)
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
{
|
||||
Buffer.MemoryCopy(ptr, (byte*)characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes);
|
||||
}
|
||||
}
|
||||
=> Write((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)characterAddress);
|
||||
|
||||
public unsafe void WriteBytes(byte[] array, int offset = 0)
|
||||
{
|
||||
|
|
@ -1,108 +1,107 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public enum CustomizationId : byte
|
||||
{
|
||||
public enum CustomizationId : byte
|
||||
{
|
||||
Race = 0,
|
||||
Gender = 1,
|
||||
BodyType = 2,
|
||||
Height = 3,
|
||||
Clan = 4,
|
||||
Face = 5,
|
||||
Hairstyle = 6,
|
||||
HighlightsOnFlag = 7,
|
||||
SkinColor = 8,
|
||||
EyeColorR = 9,
|
||||
HairColor = 10,
|
||||
HighlightColor = 11,
|
||||
FacialFeaturesTattoos = 12, // Bitmask, 1-7 per face, 8 is 1.0 tattoo
|
||||
TattooColor = 13,
|
||||
Eyebrows = 14,
|
||||
EyeColorL = 15,
|
||||
EyeShape = 16, // Flag 128 for Small
|
||||
Nose = 17,
|
||||
Jaw = 18,
|
||||
Mouth = 19, // Flag 128 for Lip Color set
|
||||
LipColor = 20, // Flag 128 for Light instead of Dark
|
||||
MuscleToneOrTailEarLength = 21,
|
||||
TailEarShape = 22,
|
||||
BustSize = 23,
|
||||
FacePaint = 24,
|
||||
FacePaintColor = 25, // Flag 128 for Light instead of Dark.
|
||||
}
|
||||
|
||||
public static class CustomizationExtensions
|
||||
{
|
||||
public static string ToDefaultName(this CustomizationId customizationId)
|
||||
=> customizationId switch
|
||||
{
|
||||
CustomizationId.Race => "Race",
|
||||
CustomizationId.Gender => "Gender",
|
||||
CustomizationId.BodyType => "Body Type",
|
||||
CustomizationId.Height => "Height",
|
||||
CustomizationId.Clan => "Clan",
|
||||
CustomizationId.Face => "Head Style",
|
||||
CustomizationId.Hairstyle => "Hair Style",
|
||||
CustomizationId.HighlightsOnFlag => "Highlights",
|
||||
CustomizationId.SkinColor => "Skin Color",
|
||||
CustomizationId.EyeColorR => "Right Eye Color",
|
||||
CustomizationId.HairColor => "Hair Color",
|
||||
CustomizationId.HighlightColor => "Highlights Color",
|
||||
CustomizationId.FacialFeaturesTattoos => "Facial Features",
|
||||
CustomizationId.TattooColor => "Tattoo Color",
|
||||
CustomizationId.Eyebrows => "Eyebrow Style",
|
||||
CustomizationId.EyeColorL => "Left Eye Color",
|
||||
CustomizationId.EyeShape => "Eye Shape",
|
||||
CustomizationId.Nose => "Nose Style",
|
||||
CustomizationId.Jaw => "Jaw Style",
|
||||
CustomizationId.Mouth => "Mouth Style",
|
||||
CustomizationId.MuscleToneOrTailEarLength => "Muscle Tone",
|
||||
CustomizationId.TailEarShape => "Tail Shape",
|
||||
CustomizationId.BustSize => "Bust Size",
|
||||
CustomizationId.FacePaint => "Face Paint",
|
||||
CustomizationId.FacePaintColor => "Face Paint Color",
|
||||
CustomizationId.LipColor => "Lip Color",
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
|
||||
public static CharaMakeParams.MenuType ToType(this CustomizationId customizationId, Race race = Race.Hyur)
|
||||
=> customizationId switch
|
||||
{
|
||||
CustomizationId.Race => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Gender => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.BodyType => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Height => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.Clan => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Face => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Hairstyle => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.HighlightsOnFlag => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.SkinColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.EyeColorR => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.HairColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.HighlightColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.FacialFeaturesTattoos => CharaMakeParams.MenuType.MultiIconSelector,
|
||||
CustomizationId.TattooColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.Eyebrows => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.EyeColorL => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.EyeShape => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Nose => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Jaw => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Mouth => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.MuscleToneOrTailEarLength => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.BustSize => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.FacePaint => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.FacePaintColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
|
||||
CustomizationId.TailEarShape => race == Race.Elezen || race == Race.Lalafell
|
||||
? CharaMakeParams.MenuType.ListSelector
|
||||
: CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.LipColor => race == Race.Hrothgar
|
||||
? CharaMakeParams.MenuType.IconSelector
|
||||
: CharaMakeParams.MenuType.ColorPicker,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
}
|
||||
Race = 0,
|
||||
Gender = 1,
|
||||
BodyType = 2,
|
||||
Height = 3,
|
||||
Clan = 4,
|
||||
Face = 5,
|
||||
Hairstyle = 6,
|
||||
HighlightsOnFlag = 7,
|
||||
SkinColor = 8,
|
||||
EyeColorR = 9,
|
||||
HairColor = 10,
|
||||
HighlightColor = 11,
|
||||
FacialFeaturesTattoos = 12, // Bitmask, 1-7 per face, 8 is 1.0 tattoo
|
||||
TattooColor = 13,
|
||||
Eyebrows = 14,
|
||||
EyeColorL = 15,
|
||||
EyeShape = 16, // Flag 128 for Small
|
||||
Nose = 17,
|
||||
Jaw = 18,
|
||||
Mouth = 19, // Flag 128 for Lip Color set
|
||||
LipColor = 20, // Flag 128 for Light instead of Dark
|
||||
MuscleToneOrTailEarLength = 21,
|
||||
TailEarShape = 22,
|
||||
BustSize = 23,
|
||||
FacePaint = 24,
|
||||
FacePaintColor = 25, // Flag 128 for Light instead of Dark.
|
||||
}
|
||||
|
||||
public static class CustomizationExtensions
|
||||
{
|
||||
public static string ToDefaultName(this CustomizationId customizationId)
|
||||
=> customizationId switch
|
||||
{
|
||||
CustomizationId.Race => "Race",
|
||||
CustomizationId.Gender => "Gender",
|
||||
CustomizationId.BodyType => "Body Type",
|
||||
CustomizationId.Height => "Height",
|
||||
CustomizationId.Clan => "Clan",
|
||||
CustomizationId.Face => "Head Style",
|
||||
CustomizationId.Hairstyle => "Hair Style",
|
||||
CustomizationId.HighlightsOnFlag => "Highlights",
|
||||
CustomizationId.SkinColor => "Skin Color",
|
||||
CustomizationId.EyeColorR => "Right Eye Color",
|
||||
CustomizationId.HairColor => "Hair Color",
|
||||
CustomizationId.HighlightColor => "Highlights Color",
|
||||
CustomizationId.FacialFeaturesTattoos => "Facial Features",
|
||||
CustomizationId.TattooColor => "Tattoo Color",
|
||||
CustomizationId.Eyebrows => "Eyebrow Style",
|
||||
CustomizationId.EyeColorL => "Left Eye Color",
|
||||
CustomizationId.EyeShape => "Eye Shape",
|
||||
CustomizationId.Nose => "Nose Style",
|
||||
CustomizationId.Jaw => "Jaw Style",
|
||||
CustomizationId.Mouth => "Mouth Style",
|
||||
CustomizationId.MuscleToneOrTailEarLength => "Muscle Tone",
|
||||
CustomizationId.TailEarShape => "Tail Shape",
|
||||
CustomizationId.BustSize => "Bust Size",
|
||||
CustomizationId.FacePaint => "Face Paint",
|
||||
CustomizationId.FacePaintColor => "Face Paint Color",
|
||||
CustomizationId.LipColor => "Lip Color",
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
|
||||
public static CharaMakeParams.MenuType ToType(this CustomizationId customizationId, Race race = Race.Hyur)
|
||||
=> customizationId switch
|
||||
{
|
||||
CustomizationId.Race => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Gender => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.BodyType => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Height => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.Clan => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Face => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Hairstyle => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.HighlightsOnFlag => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.SkinColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.EyeColorR => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.HairColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.HighlightColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.FacialFeaturesTattoos => CharaMakeParams.MenuType.MultiIconSelector,
|
||||
CustomizationId.TattooColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.Eyebrows => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.EyeColorL => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.EyeShape => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Nose => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Jaw => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Mouth => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.MuscleToneOrTailEarLength => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.BustSize => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.FacePaint => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.FacePaintColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
|
||||
CustomizationId.TailEarShape => race is Race.Elezen or Race.Lalafell
|
||||
? CharaMakeParams.MenuType.ListSelector
|
||||
: CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.LipColor => race == Race.Hrothgar
|
||||
? CharaMakeParams.MenuType.IconSelector
|
||||
: CharaMakeParams.MenuType.ColorPicker,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Reflection;
|
|||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Lumina.Data;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -14,360 +14,407 @@ using Race = Penumbra.GameData.Enums.Race;
|
|||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
// Generate everything about customization per tribe and gender.
|
||||
public partial class CustomizationOptions
|
||||
{
|
||||
internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray();
|
||||
// All races except for Unknown
|
||||
internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray();
|
||||
|
||||
// All tribes except for Unknown
|
||||
internal static readonly SubRace[] Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray();
|
||||
|
||||
// Two genders.
|
||||
internal static readonly Gender[] Genders =
|
||||
{
|
||||
Gender.Male,
|
||||
Gender.Female,
|
||||
};
|
||||
|
||||
// Every tribe and gender has a separate set of available customizations.
|
||||
internal CustomizationSet GetList(SubRace race, Gender gender)
|
||||
=> _list[ToIndex(race, gender)];
|
||||
=> _customizationSets[ToIndex(race, gender)];
|
||||
|
||||
// Get specific icons.
|
||||
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
||||
=> _icons.LoadIcon(id);
|
||||
|
||||
private static readonly int ListSize = Clans.Length * Genders.Length;
|
||||
private readonly IconStorage _icons;
|
||||
|
||||
private readonly CustomizationSet[] _list = new CustomizationSet[ListSize];
|
||||
private readonly IconStorage _icons;
|
||||
private static readonly int ListSize = Clans.Length * Genders.Length;
|
||||
private readonly CustomizationSet[] _customizationSets = new CustomizationSet[ListSize];
|
||||
|
||||
|
||||
// Get the index for the given pair of tribe and gender.
|
||||
private static int ToIndex(SubRace race, Gender gender)
|
||||
{
|
||||
var idx = ((int)race - 1) * Genders.Length + (gender == Gender.Female ? 1 : 0);
|
||||
if (idx < 0 || idx >= ListSize)
|
||||
ThrowException(race, gender);
|
||||
return idx;
|
||||
}
|
||||
|
||||
private static void ThrowException(SubRace race, Gender gender)
|
||||
=> throw new Exception($"Invalid customization requested for {race} {gender}.");
|
||||
}
|
||||
|
||||
private static int ToIndex(SubRace race, Gender gender)
|
||||
{
|
||||
if (race == SubRace.Unknown || gender != Gender.Female && gender != Gender.Male)
|
||||
ThrowException(race, gender);
|
||||
|
||||
var ret = (int)race - 1;
|
||||
ret = ret * Genders.Length + (gender == Gender.Female ? 1 : 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private Customization[] GetHairStyles(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var hairList = new List<Customization>(row.Unknown30);
|
||||
for (var i = 0; i < row.Unknown30; ++i)
|
||||
{
|
||||
var name = $"Unknown{66 + i * 9}";
|
||||
var customizeIdx =
|
||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||
hairList.Add(hairRow != null
|
||||
? new Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId)
|
||||
: new Customization(CustomizationId.Hairstyle, (byte)i, customizeIdx, 0));
|
||||
}
|
||||
|
||||
return hairList.ToArray();
|
||||
}
|
||||
|
||||
private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
||||
=> _cmpFile.RgbaColors.Skip(offset).Take(num)
|
||||
.Select((c, i) => new Customization(id, (byte)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||
.ToArray();
|
||||
|
||||
private (Customization[], Customization[]) GetColors(SubRace race, Gender gender)
|
||||
{
|
||||
if (race > SubRace.Veena || race == SubRace.Unknown)
|
||||
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
||||
|
||||
var gv = gender == Gender.Male ? 0 : 1;
|
||||
var idx = ((int)race * 2 + gv) * 5 + 3;
|
||||
|
||||
return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192),
|
||||
CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192));
|
||||
}
|
||||
|
||||
private Customization FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||
{
|
||||
var row = _customizeSheet.GetRow(value);
|
||||
return row == null
|
||||
? new Customization(id, (byte)(index + 1), value, 0)
|
||||
: new Customization(id, row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||
}
|
||||
|
||||
private static int GetListSize(CharaMakeParams row, CustomizationId id)
|
||||
{
|
||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == id);
|
||||
return menu?.Size ?? 0;
|
||||
}
|
||||
|
||||
private Customization[] GetFacePaints(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var paintList = new List<Customization>(row.Unknown37);
|
||||
for (var i = 0; i < row.Unknown37; ++i)
|
||||
{
|
||||
var name = $"Unknown{73 + i * 9}";
|
||||
var customizeIdx =
|
||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
||||
paintList.Add(paintRow != null
|
||||
? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId)
|
||||
: new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx, 0));
|
||||
}
|
||||
|
||||
return paintList.ToArray();
|
||||
}
|
||||
|
||||
private Customization[] GetTailEarShapes(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] GetFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] HrothgarFurPattern(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] HrothgarFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Hairstyle)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Hairstyle, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private CustomizationSet GetSet(SubRace race, Gender gender)
|
||||
{
|
||||
var (skin, hair) = GetColors(race, gender);
|
||||
var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var set = new CustomizationSet(race, gender)
|
||||
{
|
||||
HairStyles = GetHairStyles(race, gender),
|
||||
HairColors = hair,
|
||||
SkinColors = skin,
|
||||
EyeColors = _eyeColorPicker,
|
||||
HighlightColors = _highlightPicker,
|
||||
TattooColors = _tattooColorPicker,
|
||||
LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
||||
LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
|
||||
FacePaintColorsDark = _facePaintColorPickerDark,
|
||||
FacePaintColorsLight = _facePaintColorPickerLight,
|
||||
Faces = GetFaces(row),
|
||||
NumEyebrows = GetListSize(row, CustomizationId.Eyebrows),
|
||||
NumEyeShapes = GetListSize(row, CustomizationId.EyeShape),
|
||||
NumNoseShapes = GetListSize(row, CustomizationId.Nose),
|
||||
NumJawShapes = GetListSize(row, CustomizationId.Jaw),
|
||||
NumMouthShapes = GetListSize(row, CustomizationId.Mouth),
|
||||
FacePaints = GetFacePaints(race, gender),
|
||||
TailEarShapes = GetTailEarShapes(row),
|
||||
};
|
||||
|
||||
if (GetListSize(row, CustomizationId.BustSize) > 0)
|
||||
set.SetAvailable(CustomizationId.BustSize);
|
||||
if (GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0)
|
||||
set.SetAvailable(CustomizationId.MuscleToneOrTailEarLength);
|
||||
|
||||
if (set.NumEyebrows > 0)
|
||||
set.SetAvailable(CustomizationId.Eyebrows);
|
||||
if (set.NumEyeShapes > 0)
|
||||
set.SetAvailable(CustomizationId.EyeShape);
|
||||
if (set.NumNoseShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Nose);
|
||||
if (set.NumJawShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Jaw);
|
||||
if (set.NumMouthShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Mouth);
|
||||
if (set.FacePaints.Count > 0)
|
||||
{
|
||||
set.SetAvailable(CustomizationId.FacePaint);
|
||||
set.SetAvailable(CustomizationId.FacePaintColor);
|
||||
}
|
||||
|
||||
if (set.TailEarShapes.Count > 0)
|
||||
set.SetAvailable(CustomizationId.TailEarShape);
|
||||
if (set.Faces.Count > 0)
|
||||
set.SetAvailable(CustomizationId.Face);
|
||||
|
||||
var count = set.Faces.Count;
|
||||
var featureDict = new List<IReadOnlyList<Customization>>(count);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
||||
=> new Customization(CustomizationId.FacialFeaturesTattoos, (byte)(1 << idx), val, (ushort)(i * 8 + idx)))
|
||||
.Append(new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort)((i + 1) * 8)))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
set.FeaturesTattoos = featureDict;
|
||||
|
||||
var nameArray = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
if (menu == null)
|
||||
{
|
||||
if (c == CustomizationId.HighlightsOnFlag)
|
||||
return _lobby.GetRow(237)?.Text.ToString() ?? "Highlights";
|
||||
|
||||
return c.ToDefaultName();
|
||||
}
|
||||
|
||||
if (c == CustomizationId.FacialFeaturesTattoos)
|
||||
return
|
||||
$"{_lobby.GetRow(1741)?.Text.ToString() ?? "Facial Features"} & {_lobby.GetRow(1742)?.Text.ToString() ?? "Tattoos"}";
|
||||
|
||||
var textRow = _lobby.GetRow(menu.Value.Id);
|
||||
return textRow?.Text.ToString() ?? c.ToDefaultName();
|
||||
}).ToArray();
|
||||
nameArray[(int)CustomizationId.EyeColorL] = nameArray[(int)CustomizationId.EyeColorR];
|
||||
nameArray[(int)CustomizationId.EyeColorR] = GetName(CustomName.OddEyes);
|
||||
set.OptionName = nameArray;
|
||||
|
||||
set.Types = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case CustomizationId.HighlightColor:
|
||||
case CustomizationId.EyeColorL:
|
||||
case CustomizationId.EyeColorR:
|
||||
return CharaMakeParams.MenuType.ColorPicker;
|
||||
}
|
||||
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
||||
}).ToArray();
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
|
||||
private readonly ExcelSheet<CharaMakeParams> _listSheet;
|
||||
private readonly ExcelSheet<HairMakeType> _hairSheet;
|
||||
private readonly ExcelSheet<Lobby> _lobby;
|
||||
private readonly CmpFile _cmpFile;
|
||||
private readonly Customization[] _highlightPicker;
|
||||
private readonly Customization[] _eyeColorPicker;
|
||||
private readonly Customization[] _facePaintColorPickerDark;
|
||||
private readonly Customization[] _facePaintColorPickerLight;
|
||||
private readonly Customization[] _lipColorPickerDark;
|
||||
private readonly Customization[] _lipColorPickerLight;
|
||||
private readonly Customization[] _tattooColorPicker;
|
||||
private readonly string[] _names = new string[(int)CustomName.Num];
|
||||
public partial class CustomizationOptions
|
||||
{
|
||||
internal readonly bool Valid;
|
||||
|
||||
public string GetName(CustomName name)
|
||||
=> _names[(int)name];
|
||||
|
||||
private static Language FromClientLanguage(ClientLanguage language)
|
||||
=> language switch
|
||||
{
|
||||
ClientLanguage.English => Language.English,
|
||||
ClientLanguage.French => Language.French,
|
||||
ClientLanguage.German => Language.German,
|
||||
ClientLanguage.Japanese => Language.Japanese,
|
||||
_ => Language.English,
|
||||
};
|
||||
|
||||
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cmpFile = new CmpFile(gameData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n"
|
||||
+ "======== This usually indicates an error with your index files caused by TexTools modifications.\n"
|
||||
+ "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e);
|
||||
}
|
||||
|
||||
_customizeSheet = gameData.GetExcelSheet<CharaMakeCustomize>()!;
|
||||
_lobby = gameData.GetExcelSheet<Lobby>()!;
|
||||
var tmp = gameData.Excel.GetType()!.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.MakeGenericMethod(typeof(CharaMakeParams))!.Invoke(gameData.Excel, new object?[]
|
||||
{
|
||||
"charamaketype",
|
||||
FromClientLanguage(language),
|
||||
null,
|
||||
}) as ExcelSheet<CharaMakeParams>;
|
||||
_listSheet = tmp!;
|
||||
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
||||
SetNames(gameData);
|
||||
|
||||
_highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192);
|
||||
_lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96);
|
||||
_lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true);
|
||||
_eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192);
|
||||
_facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96);
|
||||
_facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true);
|
||||
_tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192);
|
||||
|
||||
_icons = new IconStorage(pi, gameData, _list.Length * 50);
|
||||
var tmp = new TemporaryData(gameData, this, language);
|
||||
_icons = new IconStorage(pi, gameData, _customizationSets.Length * 50);
|
||||
Valid = tmp.Valid;
|
||||
SetNames(gameData, tmp);
|
||||
foreach (var race in Clans)
|
||||
{
|
||||
foreach (var gender in Genders)
|
||||
_list[ToIndex(race, gender)] = GetSet(race, gender);
|
||||
_customizationSets[ToIndex(race, gender)] = tmp.GetSet(race, gender);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetNames(DataManager gameData)
|
||||
|
||||
// Obtain localized names of customization options and race names from the game data.
|
||||
private readonly string[] _names = new string[Enum.GetValues<CustomName>().Length];
|
||||
|
||||
private void SetNames(DataManager gameData, TemporaryData tmp)
|
||||
{
|
||||
var subRace = gameData.GetExcelSheet<Tribe>()!;
|
||||
_names[(int)CustomName.Clan] = _lobby.GetRow(102)?.Text ?? "Clan";
|
||||
_names[(int)CustomName.Gender] = _lobby.GetRow(103)?.Text ?? "Gender";
|
||||
_names[(int)CustomName.Reverse] = _lobby.GetRow(2135)?.Text ?? "Reverse";
|
||||
_names[(int)CustomName.OddEyes] = _lobby.GetRow(2125)?.Text ?? "Odd Eyes";
|
||||
_names[(int)CustomName.IrisSmall] = _lobby.GetRow(1076)?.Text ?? "Small";
|
||||
_names[(int)CustomName.IrisLarge] = _lobby.GetRow(1075)?.Text ?? "Large";
|
||||
_names[(int)CustomName.IrisSize] = _lobby.GetRow(244)?.Text ?? "Iris Size";
|
||||
_names[(int)CustomName.MidlanderM] = subRace.GetRow((int)SubRace.Midlander)?.Masculine.ToString() ?? SubRace.Midlander.ToName();
|
||||
_names[(int)CustomName.MidlanderF] = subRace.GetRow((int)SubRace.Midlander)?.Feminine.ToString() ?? SubRace.Midlander.ToName();
|
||||
_names[(int)CustomName.HighlanderM] =
|
||||
subRace.GetRow((int)SubRace.Highlander)?.Masculine.ToString() ?? SubRace.Highlander.ToName();
|
||||
_names[(int)CustomName.HighlanderF] = subRace.GetRow((int)SubRace.Highlander)?.Feminine.ToString() ?? SubRace.Highlander.ToName();
|
||||
_names[(int)CustomName.WildwoodM] = subRace.GetRow((int)SubRace.Wildwood)?.Masculine.ToString() ?? SubRace.Wildwood.ToName();
|
||||
_names[(int)CustomName.WildwoodF] = subRace.GetRow((int)SubRace.Wildwood)?.Feminine.ToString() ?? SubRace.Wildwood.ToName();
|
||||
_names[(int)CustomName.DuskwightM] = subRace.GetRow((int)SubRace.Duskwight)?.Masculine.ToString() ?? SubRace.Duskwight.ToName();
|
||||
_names[(int)CustomName.DuskwightF] = subRace.GetRow((int)SubRace.Duskwight)?.Feminine.ToString() ?? SubRace.Duskwight.ToName();
|
||||
_names[(int)CustomName.PlainsfolkM] =
|
||||
subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine.ToString() ?? SubRace.Plainsfolk.ToName();
|
||||
_names[(int)CustomName.PlainsfolkF] = subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine.ToString() ?? SubRace.Plainsfolk.ToName();
|
||||
_names[(int)CustomName.DunesfolkM] = subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine.ToString() ?? SubRace.Dunesfolk.ToName();
|
||||
_names[(int)CustomName.DunesfolkF] = subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine.ToString() ?? SubRace.Dunesfolk.ToName();
|
||||
_names[(int)CustomName.SeekerOfTheSunM] =
|
||||
subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
||||
_names[(int)CustomName.SeekerOfTheSunF] =
|
||||
subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
||||
_names[(int)CustomName.KeeperOfTheMoonM] =
|
||||
subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
||||
_names[(int)CustomName.KeeperOfTheMoonF] =
|
||||
subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
||||
_names[(int)CustomName.SeawolfM] = subRace.GetRow((int)SubRace.Seawolf)?.Masculine.ToString() ?? SubRace.Seawolf.ToName();
|
||||
_names[(int)CustomName.SeawolfF] = subRace.GetRow((int)SubRace.Seawolf)?.Feminine.ToString() ?? SubRace.Seawolf.ToName();
|
||||
_names[(int)CustomName.HellsguardM] =
|
||||
subRace.GetRow((int)SubRace.Hellsguard)?.Masculine.ToString() ?? SubRace.Hellsguard.ToName();
|
||||
_names[(int)CustomName.HellsguardF] = subRace.GetRow((int)SubRace.Hellsguard)?.Feminine.ToString() ?? SubRace.Hellsguard.ToName();
|
||||
_names[(int)CustomName.RaenM] = subRace.GetRow((int)SubRace.Raen)?.Masculine.ToString() ?? SubRace.Raen.ToName();
|
||||
_names[(int)CustomName.RaenF] = subRace.GetRow((int)SubRace.Raen)?.Feminine.ToString() ?? SubRace.Raen.ToName();
|
||||
_names[(int)CustomName.XaelaM] = subRace.GetRow((int)SubRace.Xaela)?.Masculine.ToString() ?? SubRace.Xaela.ToName();
|
||||
_names[(int)CustomName.XaelaF] = subRace.GetRow((int)SubRace.Xaela)?.Feminine.ToString() ?? SubRace.Xaela.ToName();
|
||||
_names[(int)CustomName.HelionM] = subRace.GetRow((int)SubRace.Helion)?.Masculine.ToString() ?? SubRace.Helion.ToName();
|
||||
_names[(int)CustomName.HelionF] = subRace.GetRow((int)SubRace.Helion)?.Feminine.ToString() ?? SubRace.Helion.ToName();
|
||||
_names[(int)CustomName.LostM] = subRace.GetRow((int)SubRace.Lost)?.Masculine.ToString() ?? SubRace.Lost.ToName();
|
||||
_names[(int)CustomName.LostF] = subRace.GetRow((int)SubRace.Lost)?.Feminine.ToString() ?? SubRace.Lost.ToName();
|
||||
_names[(int)CustomName.RavaM] = subRace.GetRow((int)SubRace.Rava)?.Masculine.ToString() ?? SubRace.Rava.ToName();
|
||||
_names[(int)CustomName.RavaF] = subRace.GetRow((int)SubRace.Rava)?.Feminine.ToString() ?? SubRace.Rava.ToName();
|
||||
_names[(int)CustomName.VeenaM] = subRace.GetRow((int)SubRace.Veena)?.Masculine.ToString() ?? SubRace.Veena.ToName();
|
||||
_names[(int)CustomName.VeenaF] = subRace.GetRow((int)SubRace.Veena)?.Feminine.ToString() ?? SubRace.Veena.ToName();
|
||||
|
||||
void Set(CustomName id, Lumina.Text.SeString? s, string def)
|
||||
=> _names[(int)id] = s?.ToDalamudString().TextValue ?? def;
|
||||
|
||||
Set(CustomName.Clan, tmp.Lobby.GetRow(102)?.Text, "Clan");
|
||||
Set(CustomName.Gender, tmp.Lobby.GetRow(103)?.Text, "Gender");
|
||||
Set(CustomName.Reverse, tmp.Lobby.GetRow(2135)?.Text, "Reverse");
|
||||
Set(CustomName.OddEyes, tmp.Lobby.GetRow(2125)?.Text, "Odd Eyes");
|
||||
Set(CustomName.IrisSmall, tmp.Lobby.GetRow(1076)?.Text, "Small");
|
||||
Set(CustomName.IrisLarge, tmp.Lobby.GetRow(1075)?.Text, "Large");
|
||||
Set(CustomName.IrisSize, tmp.Lobby.GetRow(244)?.Text, "Iris Size");
|
||||
Set(CustomName.MidlanderM, subRace.GetRow((int)SubRace.Midlander)?.Masculine, SubRace.Midlander.ToName());
|
||||
Set(CustomName.MidlanderF, subRace.GetRow((int)SubRace.Midlander)?.Feminine, SubRace.Midlander.ToName());
|
||||
Set(CustomName.HighlanderM, subRace.GetRow((int)SubRace.Highlander)?.Masculine, SubRace.Highlander.ToName());
|
||||
Set(CustomName.HighlanderF, subRace.GetRow((int)SubRace.Highlander)?.Feminine, SubRace.Highlander.ToName());
|
||||
Set(CustomName.WildwoodM, subRace.GetRow((int)SubRace.Wildwood)?.Masculine, SubRace.Wildwood.ToName());
|
||||
Set(CustomName.WildwoodF, subRace.GetRow((int)SubRace.Wildwood)?.Feminine, SubRace.Wildwood.ToName());
|
||||
Set(CustomName.DuskwightM, subRace.GetRow((int)SubRace.Duskwight)?.Masculine, SubRace.Duskwight.ToName());
|
||||
Set(CustomName.DuskwightF, subRace.GetRow((int)SubRace.Duskwight)?.Feminine, SubRace.Duskwight.ToName());
|
||||
Set(CustomName.PlainsfolkM, subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine, SubRace.Plainsfolk.ToName());
|
||||
Set(CustomName.PlainsfolkF, subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine, SubRace.Plainsfolk.ToName());
|
||||
Set(CustomName.DunesfolkM, subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine, SubRace.Dunesfolk.ToName());
|
||||
Set(CustomName.DunesfolkF, subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine, SubRace.Dunesfolk.ToName());
|
||||
Set(CustomName.SeekerOfTheSunM, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine, SubRace.SeekerOfTheSun.ToName());
|
||||
Set(CustomName.SeekerOfTheSunF, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine, SubRace.SeekerOfTheSun.ToName());
|
||||
Set(CustomName.KeeperOfTheMoonM, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine, SubRace.KeeperOfTheMoon.ToName());
|
||||
Set(CustomName.KeeperOfTheMoonF, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine, SubRace.KeeperOfTheMoon.ToName());
|
||||
Set(CustomName.SeawolfM, subRace.GetRow((int)SubRace.Seawolf)?.Masculine, SubRace.Seawolf.ToName());
|
||||
Set(CustomName.SeawolfF, subRace.GetRow((int)SubRace.Seawolf)?.Feminine, SubRace.Seawolf.ToName());
|
||||
Set(CustomName.HellsguardM, subRace.GetRow((int)SubRace.Hellsguard)?.Masculine, SubRace.Hellsguard.ToName());
|
||||
Set(CustomName.HellsguardF, subRace.GetRow((int)SubRace.Hellsguard)?.Feminine, SubRace.Hellsguard.ToName());
|
||||
Set(CustomName.RaenM, subRace.GetRow((int)SubRace.Raen)?.Masculine, SubRace.Raen.ToName());
|
||||
Set(CustomName.RaenF, subRace.GetRow((int)SubRace.Raen)?.Feminine, SubRace.Raen.ToName());
|
||||
Set(CustomName.XaelaM, subRace.GetRow((int)SubRace.Xaela)?.Masculine, SubRace.Xaela.ToName());
|
||||
Set(CustomName.XaelaF, subRace.GetRow((int)SubRace.Xaela)?.Feminine, SubRace.Xaela.ToName());
|
||||
Set(CustomName.HelionM, subRace.GetRow((int)SubRace.Helion)?.Masculine, SubRace.Helion.ToName());
|
||||
Set(CustomName.HelionF, subRace.GetRow((int)SubRace.Helion)?.Feminine, SubRace.Helion.ToName());
|
||||
Set(CustomName.LostM, subRace.GetRow((int)SubRace.Lost)?.Masculine, SubRace.Lost.ToName());
|
||||
Set(CustomName.LostF, subRace.GetRow((int)SubRace.Lost)?.Feminine, SubRace.Lost.ToName());
|
||||
Set(CustomName.RavaM, subRace.GetRow((int)SubRace.Rava)?.Masculine, SubRace.Rava.ToName());
|
||||
Set(CustomName.RavaF, subRace.GetRow((int)SubRace.Rava)?.Feminine, SubRace.Rava.ToName());
|
||||
Set(CustomName.VeenaM, subRace.GetRow((int)SubRace.Veena)?.Masculine, SubRace.Veena.ToName());
|
||||
Set(CustomName.VeenaF, subRace.GetRow((int)SubRace.Veena)?.Feminine, SubRace.Veena.ToName());
|
||||
}
|
||||
|
||||
private class TemporaryData
|
||||
{
|
||||
public bool Valid
|
||||
=> _cmpFile.Valid;
|
||||
|
||||
public CustomizationSet GetSet(SubRace race, Gender gender)
|
||||
{
|
||||
var (skin, hair) = GetColors(race, gender);
|
||||
var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
|
||||
// Create the initial set with all the easily accessible parameters available for anyone.
|
||||
var set = new CustomizationSet(race, gender)
|
||||
{
|
||||
HairStyles = GetHairStyles(race, gender),
|
||||
HairColors = hair,
|
||||
SkinColors = skin,
|
||||
EyeColors = _eyeColorPicker,
|
||||
HighlightColors = _highlightPicker,
|
||||
TattooColors = _tattooColorPicker,
|
||||
LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
||||
LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
|
||||
FacePaintColorsDark = _facePaintColorPickerDark,
|
||||
FacePaintColorsLight = _facePaintColorPickerLight,
|
||||
Faces = GetFaces(row),
|
||||
NumEyebrows = GetListSize(row, CustomizationId.Eyebrows),
|
||||
NumEyeShapes = GetListSize(row, CustomizationId.EyeShape),
|
||||
NumNoseShapes = GetListSize(row, CustomizationId.Nose),
|
||||
NumJawShapes = GetListSize(row, CustomizationId.Jaw),
|
||||
NumMouthShapes = GetListSize(row, CustomizationId.Mouth),
|
||||
FacePaints = GetFacePaints(race, gender),
|
||||
TailEarShapes = GetTailEarShapes(row),
|
||||
};
|
||||
|
||||
SetAvailability(set, row);
|
||||
SetFacialFeatures(set, row);
|
||||
SetMenuTypes(set, row);
|
||||
SetNames(set, row);
|
||||
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public TemporaryData(DataManager gameData, CustomizationOptions options, ClientLanguage language)
|
||||
{
|
||||
_options = options;
|
||||
_cmpFile = new CmpFile(gameData);
|
||||
_customizeSheet = gameData.GetExcelSheet<CharaMakeCustomize>()!;
|
||||
Lobby = gameData.GetExcelSheet<Lobby>()!;
|
||||
var tmp = gameData.Excel.GetType().GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)?
|
||||
.MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[]
|
||||
{
|
||||
"charamaketype",
|
||||
language.ToLumina(),
|
||||
null,
|
||||
}) as ExcelSheet<CharaMakeParams>;
|
||||
_listSheet = tmp!;
|
||||
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
||||
_highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192);
|
||||
_lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96);
|
||||
_lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true);
|
||||
_eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192);
|
||||
_facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96);
|
||||
_facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true);
|
||||
_tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192);
|
||||
}
|
||||
|
||||
// Required sheets.
|
||||
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
|
||||
private readonly ExcelSheet<CharaMakeParams> _listSheet;
|
||||
private readonly ExcelSheet<HairMakeType> _hairSheet;
|
||||
public readonly ExcelSheet<Lobby> Lobby;
|
||||
private readonly CmpFile _cmpFile;
|
||||
|
||||
// Those values are shared between all races.
|
||||
private readonly Customization[] _highlightPicker;
|
||||
private readonly Customization[] _eyeColorPicker;
|
||||
private readonly Customization[] _facePaintColorPickerDark;
|
||||
private readonly Customization[] _facePaintColorPickerLight;
|
||||
private readonly Customization[] _lipColorPickerDark;
|
||||
private readonly Customization[] _lipColorPickerLight;
|
||||
private readonly Customization[] _tattooColorPicker;
|
||||
|
||||
private readonly CustomizationOptions _options;
|
||||
|
||||
private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
||||
=> _cmpFile.GetSlice(offset, num)
|
||||
.Select((c, i) => new Customization(id, (byte)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||
.ToArray();
|
||||
|
||||
private static void SetMenuTypes(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
// Set up the menu types for all customizations.
|
||||
set.Types = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
// Those types are not correctly given in the menu, so special case them to color pickers.
|
||||
switch (c)
|
||||
{
|
||||
case CustomizationId.HighlightColor:
|
||||
case CustomizationId.EyeColorL:
|
||||
case CustomizationId.EyeColorR:
|
||||
return CharaMakeParams.MenuType.ColorPicker;
|
||||
}
|
||||
|
||||
// Otherwise find the first menu corresponding to the id.
|
||||
// If there is none, assume a list.
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
||||
}).ToArray();
|
||||
set.Order = CustomizationSet.ComputeOrder(set);
|
||||
}
|
||||
|
||||
// Set customizations available if they have any options.
|
||||
private static void SetAvailability(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
void Set(bool available, CustomizationId flag)
|
||||
{
|
||||
if (available)
|
||||
set.SetAvailable(flag);
|
||||
}
|
||||
|
||||
// Both are percentages that are either unavailable or 0-100.
|
||||
Set(GetListSize(row, CustomizationId.BustSize) > 0, CustomizationId.BustSize);
|
||||
Set(GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0, CustomizationId.MuscleToneOrTailEarLength);
|
||||
Set(set.NumEyebrows > 0, CustomizationId.Eyebrows);
|
||||
Set(set.NumEyeShapes > 0, CustomizationId.EyeShape);
|
||||
Set(set.NumNoseShapes > 0, CustomizationId.Nose);
|
||||
Set(set.NumJawShapes > 0, CustomizationId.Jaw);
|
||||
Set(set.NumMouthShapes > 0, CustomizationId.Mouth);
|
||||
Set(set.TailEarShapes.Count > 0, CustomizationId.TailEarShape);
|
||||
Set(set.Faces.Count > 0, CustomizationId.Face);
|
||||
Set(set.FacePaints.Count > 0, CustomizationId.FacePaint);
|
||||
Set(set.FacePaints.Count > 0, CustomizationId.FacePaintColor);
|
||||
}
|
||||
|
||||
// Create a list of lists of facial features and the legacy tattoo.
|
||||
private static void SetFacialFeatures(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
var count = set.Faces.Count;
|
||||
var featureDict = new List<IReadOnlyList<Customization>>(count);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var legacyTattoo = new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort)((i + 1) * 8));
|
||||
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
||||
=> new Customization(CustomizationId.FacialFeaturesTattoos, (byte)(1 << idx), val, (ushort)(i * 8 + idx)))
|
||||
.Append(legacyTattoo)
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
set.FeaturesTattoos = featureDict.ToArray();
|
||||
}
|
||||
|
||||
// Set the names for the given set of parameters.
|
||||
private void SetNames(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
var nameArray = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
// Find the first menu that corresponds to the Id.
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
if (menu == null)
|
||||
{
|
||||
// If none exists and the id corresponds to highlights, set the Highlights name.
|
||||
if (c == CustomizationId.HighlightsOnFlag)
|
||||
return Lobby.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights";
|
||||
|
||||
// Otherwise there is an error and we use the default name.
|
||||
return c.ToDefaultName();
|
||||
}
|
||||
|
||||
// Facial Features and Tattoos is created by combining two strings.
|
||||
if (c == CustomizationId.FacialFeaturesTattoos)
|
||||
return
|
||||
$"{Lobby.GetRow(1741)?.Text.ToDalamudString().ToString() ?? "Facial Features"} & {Lobby.GetRow(1742)?.Text.ToDalamudString().ToString() ?? "Tattoos"}";
|
||||
|
||||
// Otherwise all is normal, get the menu name or if it does not work the default name.
|
||||
var textRow = Lobby.GetRow(menu.Value.Id);
|
||||
return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName();
|
||||
}).ToArray();
|
||||
|
||||
// Add names for both eye colors.
|
||||
nameArray[(int)CustomizationId.EyeColorL] = nameArray[(int)CustomizationId.EyeColorR];
|
||||
nameArray[(int)CustomizationId.EyeColorR] = _options.GetName(CustomName.OddEyes);
|
||||
|
||||
set.OptionName = nameArray;
|
||||
}
|
||||
|
||||
// Obtain available skin and hair colors for the given subrace and gender.
|
||||
private (Customization[], Customization[]) GetColors(SubRace race, Gender gender)
|
||||
{
|
||||
if (race is > SubRace.Veena or SubRace.Unknown)
|
||||
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
||||
|
||||
var gv = gender == Gender.Male ? 0 : 1;
|
||||
var idx = ((int)race * 2 + gv) * 5 + 3;
|
||||
|
||||
return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192),
|
||||
CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192));
|
||||
}
|
||||
|
||||
// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender.
|
||||
private Customization[] GetHairStyles(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
// Unknown30 is the number of available hairstyles.
|
||||
var hairList = new List<Customization>(row.Unknown30);
|
||||
// Hairstyles can be found starting at Unknown66.
|
||||
for (var i = 0; i < row.Unknown30; ++i)
|
||||
{
|
||||
var name = $"Unknown{66 + i * 9}";
|
||||
var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||
hairList.Add(hairRow != null
|
||||
? new Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId)
|
||||
: new Customization(CustomizationId.Hairstyle, (byte)i, customizeIdx));
|
||||
}
|
||||
|
||||
return hairList.ToArray();
|
||||
}
|
||||
|
||||
// Get Features.
|
||||
private Customization FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||
{
|
||||
var row = _customizeSheet.GetRow(value);
|
||||
return row == null
|
||||
? new Customization(id, (byte)(index + 1), value)
|
||||
: new Customization(id, row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||
}
|
||||
|
||||
// Get List sizes.
|
||||
private static int GetListSize(CharaMakeParams row, CustomizationId id)
|
||||
{
|
||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == id);
|
||||
return menu?.Size ?? 0;
|
||||
}
|
||||
|
||||
// Get face paints from the hair sheet via reflection.
|
||||
private Customization[] GetFacePaints(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var paintList = new List<Customization>(row.Unknown37);
|
||||
|
||||
// Number of available face paints is at Unknown37.
|
||||
for (var i = 0; i < row.Unknown37; ++i)
|
||||
{
|
||||
// Face paints start at Unknown73.
|
||||
var name = $"Unknown{73 + i * 9}";
|
||||
var customizeIdx =
|
||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
||||
// Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints.
|
||||
paintList.Add(paintRow != null
|
||||
? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId)
|
||||
: new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx));
|
||||
}
|
||||
|
||||
return paintList.ToArray();
|
||||
}
|
||||
|
||||
// Specific icons for tails or ears.
|
||||
private Customization[] GetTailEarShapes(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
// Specific icons for faces.
|
||||
private Customization[] GetFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
// Specific icons for Hrothgar patterns.
|
||||
private Customization[] HrothgarFurPattern(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
// Each Subrace and Gender combo has a customization set.
|
||||
// This describes the available customizations, their types and their names.
|
||||
public class CustomizationSet
|
||||
{
|
||||
public const int DefaultAvailable =
|
||||
(1 << (int)CustomizationId.Height)
|
||||
| (1 << (int)CustomizationId.Hairstyle)
|
||||
| (1 << (int)CustomizationId.SkinColor)
|
||||
| (1 << (int)CustomizationId.EyeColorR)
|
||||
| (1 << (int)CustomizationId.EyeColorL)
|
||||
| (1 << (int)CustomizationId.HairColor)
|
||||
| (1 << (int)CustomizationId.HighlightColor)
|
||||
| (1 << (int)CustomizationId.FacialFeaturesTattoos)
|
||||
| (1 << (int)CustomizationId.TattooColor)
|
||||
| (1 << (int)CustomizationId.LipColor)
|
||||
| (1 << (int)CustomizationId.Height);
|
||||
|
||||
internal CustomizationSet(SubRace clan, Gender gender)
|
||||
{
|
||||
Gender = gender;
|
||||
Clan = clan;
|
||||
_settingAvailable = clan.ToRace() == Race.Hrothgar && gender == Gender.Female
|
||||
? 0
|
||||
? 0u
|
||||
: DefaultAvailable;
|
||||
}
|
||||
|
||||
|
|
@ -35,39 +25,50 @@ public class CustomizationSet
|
|||
public Race Race
|
||||
=> Clan.ToRace();
|
||||
|
||||
private int _settingAvailable;
|
||||
private uint _settingAvailable;
|
||||
|
||||
internal void SetAvailable(CustomizationId id)
|
||||
=> _settingAvailable |= 1 << (int)id;
|
||||
=> _settingAvailable |= 1u << (int)id;
|
||||
|
||||
public bool IsAvailable(CustomizationId id)
|
||||
=> (_settingAvailable & (1 << (int)id)) != 0;
|
||||
=> (_settingAvailable & (1u << (int)id)) != 0;
|
||||
|
||||
public int NumEyebrows { get; internal set; }
|
||||
public int NumEyeShapes { get; internal set; }
|
||||
public int NumNoseShapes { get; internal set; }
|
||||
public int NumJawShapes { get; internal set; }
|
||||
public int NumMouthShapes { get; internal set; }
|
||||
public int NumEyebrows { get; internal init; }
|
||||
public int NumEyeShapes { get; internal init; }
|
||||
public int NumNoseShapes { get; internal init; }
|
||||
public int NumJawShapes { get; internal init; }
|
||||
public int NumMouthShapes { get; internal init; }
|
||||
|
||||
public string ToHumanReadable(CustomizationData customizationData)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var id in Enum.GetValues<CustomizationId>().Where(IsAvailable))
|
||||
sb.AppendFormat("{0,-20}", Option(id)).Append(customizationData[id]);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> Faces { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> HairStyles { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> TailEarShapes { get; internal set; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<Customization>> FeaturesTattoos { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaints { get; internal set; } = null!;
|
||||
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> Faces { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> HairStyles { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> TailEarShapes { get; internal init; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<Customization>> FeaturesTattoos { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaints { get; internal init; } = null!;
|
||||
|
||||
public IReadOnlyList<Customization> SkinColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> HairColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> HighlightColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> EyeColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> TattooColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsLight { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsDark { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsLight { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsDark { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> SkinColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> HairColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> HighlightColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> EyeColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> TattooColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsDark { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsDark { get; internal init; } = null!;
|
||||
|
||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
||||
public IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizationId[]> Order { get; internal set; } = null!;
|
||||
|
||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
||||
|
||||
public string Option(CustomizationId id)
|
||||
=> OptionName[(int)id];
|
||||
|
|
@ -154,6 +155,15 @@ public class CustomizationSet
|
|||
public CharaMakeParams.MenuType Type(CustomizationId id)
|
||||
=> Types[(int)id];
|
||||
|
||||
internal static IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizationId[]> ComputeOrder(CustomizationSet set)
|
||||
{
|
||||
var ret = (CustomizationId[])Enum.GetValues(typeof(CustomizationId));
|
||||
ret[(int)CustomizationId.TattooColor] = CustomizationId.EyeColorL;
|
||||
ret[(int)CustomizationId.EyeColorL] = CustomizationId.EyeColorR;
|
||||
ret[(int)CustomizationId.EyeColorR] = CustomizationId.TattooColor;
|
||||
|
||||
return ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
|
||||
}
|
||||
|
||||
public int Count(CustomizationId id)
|
||||
{
|
||||
|
|
@ -187,4 +197,17 @@ public class CustomizationSet
|
|||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
||||
};
|
||||
}
|
||||
|
||||
private const uint DefaultAvailable =
|
||||
(1u << (int)CustomizationId.Height)
|
||||
| (1u << (int)CustomizationId.Hairstyle)
|
||||
| (1u << (int)CustomizationId.SkinColor)
|
||||
| (1u << (int)CustomizationId.EyeColorR)
|
||||
| (1u << (int)CustomizationId.EyeColorL)
|
||||
| (1u << (int)CustomizationId.HairColor)
|
||||
| (1u << (int)CustomizationId.HighlightColor)
|
||||
| (1u << (int)CustomizationId.FacialFeaturesTattoos)
|
||||
| (1u << (int)CustomizationId.TattooColor)
|
||||
| (1u << (int)CustomizationId.LipColor)
|
||||
| (1u << (int)CustomizationId.Height);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public interface ICustomizationManager
|
||||
{
|
||||
public interface ICustomizationManager
|
||||
{
|
||||
public IReadOnlyList<Race> Races { get; }
|
||||
public IReadOnlyList<SubRace> Clans { get; }
|
||||
public IReadOnlyList<Gender> Genders { get; }
|
||||
public IReadOnlyList<Race> Races { get; }
|
||||
public IReadOnlyList<SubRace> Clans { get; }
|
||||
public IReadOnlyList<Gender> Genders { get; }
|
||||
|
||||
public CustomizationSet GetList(SubRace race, Gender gender);
|
||||
public CustomizationSet GetList(SubRace race, Gender gender);
|
||||
|
||||
public ImGuiScene.TextureWrap GetIcon(uint iconId);
|
||||
public string GetName(CustomName name);
|
||||
}
|
||||
public ImGuiScene.TextureWrap GetIcon(uint iconId);
|
||||
public string GetName(CustomName name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Glamourer.Structs;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -17,20 +20,14 @@ public static class GameData
|
|||
private static Dictionary<EquipSlot, List<Item>>? _itemsBySlot;
|
||||
private static Dictionary<byte, Job>? _jobs;
|
||||
private static Dictionary<ushort, JobGroup>? _jobGroups;
|
||||
private static SortedList<uint, ModelChara>? _models;
|
||||
private static ModelData? _models;
|
||||
private static RestrictedGear? _restrictedGear;
|
||||
|
||||
public static IReadOnlyDictionary<uint, ModelChara> Models(DataManager dataManager)
|
||||
{
|
||||
if (_models != null)
|
||||
return _models;
|
||||
public static RestrictedGear RestrictedGear(DataManager dataManager)
|
||||
=> _restrictedGear ??= new RestrictedGear(dataManager);
|
||||
|
||||
var sheet = dataManager.GetExcelSheet<ModelChara>()!;
|
||||
|
||||
_models = new SortedList<uint, ModelChara>((int)sheet.RowCount);
|
||||
foreach (var model in sheet.Where(m => m.Type != 0))
|
||||
_models.Add(model.RowId, model);
|
||||
return _models;
|
||||
}
|
||||
public static ModelData Models(DataManager dataManager)
|
||||
=> _models ??= new ModelData(dataManager);
|
||||
|
||||
public static IReadOnlyDictionary<byte, Stain> Stains(DataManager dataManager)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@
|
|||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFXIVClientStructs">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGuiScene">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
|
|
|
|||
75
Glamourer.GameData/ModelData.cs
Normal file
75
Glamourer.GameData/ModelData.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Companion = Lumina.Excel.GeneratedSheets.Companion;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public class ModelData
|
||||
{
|
||||
public struct Data
|
||||
{
|
||||
public readonly ModelChara Model;
|
||||
|
||||
public string FirstName { get; }
|
||||
public string AllNames { get; internal set; }
|
||||
|
||||
public Data(ModelChara model, string name)
|
||||
{
|
||||
Model = model;
|
||||
FirstName = $"{name} #{model.RowId:D4}";
|
||||
AllNames = $"#{model.RowId:D4}\n{name}";
|
||||
}
|
||||
}
|
||||
|
||||
private readonly SortedList<uint, Data> _models;
|
||||
|
||||
public IReadOnlyDictionary<uint, Data> Models
|
||||
=> _models;
|
||||
|
||||
public ModelData(DataManager dataManager)
|
||||
{
|
||||
var modelSheet = dataManager.GetExcelSheet<ModelChara>();
|
||||
|
||||
_models = new SortedList<uint, Data>(NpcNames.ModelCharas.Count);
|
||||
|
||||
void UpdateData(uint model, string name)
|
||||
{
|
||||
name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name);
|
||||
if (_models.TryGetValue(model, out var data))
|
||||
data.AllNames = $"{data.AllNames}\n{name}";
|
||||
else
|
||||
data = new Data(modelSheet!.GetRow(model)!, name);
|
||||
_models[model] = data;
|
||||
}
|
||||
|
||||
var companionSheet = dataManager.GetExcelSheet<Companion>()!;
|
||||
foreach (var companion in companionSheet.Where(c => c.Model.Row != 0 && c.Singular.RawData.Length > 0))
|
||||
UpdateData(companion.Model.Row, companion.Singular.ToDalamudString().TextValue);
|
||||
|
||||
var mountSheet = dataManager.GetExcelSheet<Mount>()!;
|
||||
foreach (var mount in mountSheet.Where(c => c.ModelChara.Row != 0 && c.Singular.RawData.Length > 0))
|
||||
UpdateData(mount.ModelChara.Row, mount.Singular.ToDalamudString().TextValue);
|
||||
|
||||
var bNpcNames = dataManager.GetExcelSheet<BNpcName>()!;
|
||||
foreach (var (model, list) in NpcNames.ModelCharas)
|
||||
{
|
||||
foreach (var nameId in list)
|
||||
{
|
||||
var name = nameId >= 0
|
||||
? bNpcNames.GetRow((uint)nameId)?.Singular.ToDalamudString().TextValue ?? string.Empty
|
||||
: NpcNames.Names[~nameId];
|
||||
if (name.Length == 0)
|
||||
continue;
|
||||
|
||||
UpdateData(model, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3254
Glamourer.GameData/ModelNames.cs
Normal file
3254
Glamourer.GameData/ModelNames.cs
Normal file
File diff suppressed because it is too large
Load diff
441
Glamourer.GameData/RestrictedGear.cs
Normal file
441
Glamourer.GameData/RestrictedGear.cs
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
// Handle gender- or race-locked gear in the draw model itself.
|
||||
// Racial gear gets swapped to the correct current race and gender (it is one set each).
|
||||
// Gender-locked gear gets swapped to the equivalent set if it exists (most of them do),
|
||||
// with some items getting send to emperor's new clothes and a few funny entries.
|
||||
public class RestrictedGear
|
||||
{
|
||||
private readonly ExcelSheet<Item> _items;
|
||||
private readonly ExcelSheet<EquipRaceCategory> _categories;
|
||||
|
||||
private readonly HashSet<uint> _raceGenderSet = RaceGenderGroup.Where(c => c != 0).ToHashSet();
|
||||
private readonly Dictionary<uint, uint> _maleToFemale = new();
|
||||
private readonly Dictionary<uint, uint> _femaleToMale = new();
|
||||
|
||||
internal RestrictedGear(DataManager gameData)
|
||||
{
|
||||
_items = gameData.GetExcelSheet<Item>()!;
|
||||
_categories = gameData.GetExcelSheet<EquipRaceCategory>()!;
|
||||
AddKnown();
|
||||
UnhandledRestrictedGear(false); // Set this to true to create a print of unassigned gear on launch.
|
||||
}
|
||||
|
||||
// Resolve a model given by its model id, variant and slot for your current race and gender.
|
||||
public (bool Replaced, SetId ModelId, byte Variant) ResolveRestricted(SetId modelId, byte variant, EquipSlot slot, Race race,
|
||||
Gender gender)
|
||||
{
|
||||
var quad = modelId.Value | ((uint)variant << 16);
|
||||
// Check racial gear, this does not need slots.
|
||||
if (RaceGenderGroup.Contains(quad))
|
||||
{
|
||||
var idx = ((int)race - 1) * 2 + (gender is Gender.Female or Gender.FemaleNpc ? 1 : 0);
|
||||
var value = RaceGenderGroup[idx];
|
||||
return (value != quad, (ushort)value, (byte)(value >> 16));
|
||||
}
|
||||
|
||||
// Check gender slots. If current gender is female, check if anything needs to be changed from male to female,
|
||||
// and vice versa.
|
||||
// Some items lead to the exact same model- and variant id just gender specified,
|
||||
// so check for actual difference in the Replaced bool.
|
||||
var needle = quad | ((uint)slot.ToSlot() << 24);
|
||||
if (gender is Gender.Female or Gender.FemaleNpc && _maleToFemale.TryGetValue(needle, out var newValue)
|
||||
|| gender is Gender.Male or Gender.MaleNpc && _femaleToMale.TryGetValue(needle, out newValue))
|
||||
return (quad != newValue, (ushort)newValue, (byte)(newValue >> 16));
|
||||
|
||||
// The gear is not restricted.
|
||||
return (false, modelId, variant);
|
||||
}
|
||||
|
||||
// Add all unknown restricted gear and pair it with emperor's new gear on start up.
|
||||
// Can also print unhandled items.
|
||||
public void UnhandledRestrictedGear(bool print = false)
|
||||
{
|
||||
if (print)
|
||||
PluginLog.Information("#### MALE ONLY ######");
|
||||
|
||||
void AddEmperor(Item item, bool male, bool female)
|
||||
{
|
||||
var slot = ((EquipSlot)item.EquipSlotCategory.Row).ToSlot();
|
||||
var emperor = slot switch
|
||||
{
|
||||
EquipSlot.Head => 10032u,
|
||||
EquipSlot.Body => 10033u,
|
||||
EquipSlot.Hands => 10034u,
|
||||
EquipSlot.Legs => 10035u,
|
||||
EquipSlot.Feet => 10036u,
|
||||
EquipSlot.Ears => 09293u,
|
||||
EquipSlot.Neck => 09292u,
|
||||
EquipSlot.Wrists => 09294u,
|
||||
EquipSlot.RFinger => 09295u,
|
||||
EquipSlot.LFinger => 09295u,
|
||||
_ => 0u,
|
||||
};
|
||||
if (emperor == 0)
|
||||
return;
|
||||
|
||||
if (male)
|
||||
AddItem(item.RowId, emperor, true, false);
|
||||
if (female)
|
||||
AddItem(emperor, item.RowId, false, true);
|
||||
}
|
||||
|
||||
var unhandled = 0;
|
||||
foreach (var item in _items.Where(i => i.EquipRestriction == 2))
|
||||
{
|
||||
if (_maleToFemale.ContainsKey((uint)item.ModelMain | ((uint)((EquipSlot)item.EquipSlotCategory.Row).ToSlot() << 24)))
|
||||
continue;
|
||||
|
||||
++unhandled;
|
||||
AddEmperor(item, true, false);
|
||||
|
||||
if (print)
|
||||
PluginLog.Information($"{item.RowId:D5} {item.Name.ToDalamudString().TextValue}");
|
||||
}
|
||||
|
||||
if (print)
|
||||
PluginLog.Information("#### FEMALE ONLY ####");
|
||||
foreach (var item in _items.Where(i => i.EquipRestriction == 3))
|
||||
{
|
||||
if (_femaleToMale.ContainsKey((uint)item.ModelMain | ((uint)((EquipSlot)item.EquipSlotCategory.Row).ToSlot() << 24)))
|
||||
continue;
|
||||
|
||||
++unhandled;
|
||||
AddEmperor(item, false, true);
|
||||
|
||||
if (print)
|
||||
PluginLog.Information($"{item.RowId:D5} {item.Name.ToDalamudString().TextValue}");
|
||||
}
|
||||
|
||||
if (print)
|
||||
PluginLog.Information("#### OTHER #########");
|
||||
|
||||
foreach (var item in _items.Where(i => i.EquipRestriction > 3))
|
||||
{
|
||||
if (_raceGenderSet.Contains((uint)item.ModelMain))
|
||||
continue;
|
||||
|
||||
++unhandled;
|
||||
if (print)
|
||||
PluginLog.Information(
|
||||
$"{item.RowId:D5} {item.Name.ToDalamudString().TextValue} RestrictionGroup {_categories.GetRow(item.EquipRestriction)!.RowId:D2}");
|
||||
}
|
||||
|
||||
if (unhandled > 0)
|
||||
PluginLog.Warning("There were {Num} restricted items not handled and directed to Emperor's New Set.", unhandled);
|
||||
}
|
||||
|
||||
// Add a item redirection by its item - NOT MODEL - id.
|
||||
// This uses the items model as well as its slot.
|
||||
// Creates a <-> redirection by default but can add -> or <- redirections by setting the corresponding bools to false.
|
||||
// Prints warnings if anything does not make sense.
|
||||
private void AddItem(uint itemIdMale, uint itemIdFemale, bool addMale = true, bool addFemale = true)
|
||||
{
|
||||
if (!addMale && !addFemale)
|
||||
return;
|
||||
|
||||
var mItem = _items.GetRow(itemIdMale);
|
||||
var fItem = _items.GetRow(itemIdFemale);
|
||||
if (mItem == null || fItem == null)
|
||||
{
|
||||
PluginLog.Warning($"Could not add item {itemIdMale} or {itemIdFemale} to restricted items.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mItem.EquipRestriction != 2 && addMale)
|
||||
{
|
||||
PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} is not restricted anymore.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fItem.EquipRestriction != 3 && addFemale)
|
||||
{
|
||||
PluginLog.Warning($"{fItem.Name.ToDalamudString().TextValue} is not restricted anymore.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mSlot = ((EquipSlot)mItem.EquipSlotCategory.Row).ToSlot();
|
||||
var fSlot = ((EquipSlot)fItem.EquipSlotCategory.Row).ToSlot();
|
||||
if (!mSlot.IsAccessory() && !mSlot.IsEquipment())
|
||||
{
|
||||
PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} is not equippable to a known slot.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSlot != fSlot)
|
||||
{
|
||||
PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} and {fItem.Name.ToDalamudString().TextValue} are not compatible.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mModelIdSlot = (uint)mItem.ModelMain | ((uint)mSlot << 24);
|
||||
var fModelIdSlot = (uint)fItem.ModelMain | ((uint)fSlot << 24);
|
||||
|
||||
if (addMale)
|
||||
_maleToFemale.TryAdd(mModelIdSlot, fModelIdSlot);
|
||||
if (addFemale)
|
||||
_femaleToMale.TryAdd(fModelIdSlot, mModelIdSlot);
|
||||
}
|
||||
|
||||
// Add all currently existing and known gender restricted items.
|
||||
private void AddKnown()
|
||||
{
|
||||
AddItem(02967, 02970); // Lord's Yukata (Blue) <-> Lady's Yukata (Red)
|
||||
AddItem(02968, 02971); // Lord's Yukata (Green) <-> Lady's Yukata (Blue)
|
||||
AddItem(02969, 02972); // Lord's Yukata (Grey) <-> Lady's Yukata (Black)
|
||||
AddItem(02973, 02978); // Red Summer Top <-> Red Summer Halter
|
||||
AddItem(02974, 02979); // Green Summer Top <-> Green Summer Halter
|
||||
AddItem(02975, 02980); // Blue Summer Top <-> Blue Summer Halter
|
||||
AddItem(02976, 02981); // Solar Summer Top <-> Solar Summer Halter
|
||||
AddItem(02977, 02982); // Lunar Summer Top <-> Lunar Summer Halter
|
||||
AddItem(02996, 02997); // Hempen Undershirt <-> Hempen Camise
|
||||
AddItem(03280, 03283); // Lord's Drawers (Black) <-> Lady's Knickers (Black)
|
||||
AddItem(03281, 03284); // Lord's Drawers (White) <-> Lady's Knickers (White)
|
||||
AddItem(03282, 03285); // Lord's Drawers (Gold) <-> Lady's Knickers (Gold)
|
||||
AddItem(03286, 03291); // Red Summer Trunks <-> Red Summer Tanga
|
||||
AddItem(03287, 03292); // Green Summer Trunks <-> Green Summer Tanga
|
||||
AddItem(03288, 03293); // Blue Summer Trunks <-> Blue Summer Tanga
|
||||
AddItem(03289, 03294); // Solar Summer Trunks <-> Solar Summer Tanga
|
||||
AddItem(03290, 03295); // Lunar Summer Trunks <-> Lunar Summer Tanga
|
||||
AddItem(03307, 03308); // Hempen Underpants <-> Hempen Pantalettes
|
||||
AddItem(03748, 03749); // Lord's Clogs <-> Lady's Clogs
|
||||
AddItem(06045, 06041); // Bohemian's Coat <-> Guardian Corps Coat
|
||||
AddItem(06046, 06042); // Bohemian's Gloves <-> Guardian Corps Gauntlets
|
||||
AddItem(06047, 06043); // Bohemian's Trousers <-> Guardian Corps Skirt
|
||||
AddItem(06048, 06044); // Bohemian's Boots <-> Guardian Corps Boots
|
||||
AddItem(06094, 06098); // Summer Evening Top <-> Summer Morning Halter
|
||||
AddItem(06095, 06099); // Summer Evening Trunks <-> Summer Morning Tanga
|
||||
AddItem(06096, 06100); // Striped Summer Top <-> Striped Summer Halter
|
||||
AddItem(06097, 06101); // Striped Summer Trunks <-> Striped Summer Tanga
|
||||
AddItem(06102, 06104); // Black Summer Top <-> Black Summer Halter
|
||||
AddItem(06103, 06105); // Black Summer Trunks <-> Black Summer Tanga
|
||||
AddItem(06972, 06973); // Valentione Apron <-> Valentione Apron Dress
|
||||
AddItem(06975, 06976); // Valentione Trousers <-> Valentione Skirt
|
||||
AddItem(08532, 08535); // Lord's Yukata (Blackflame) <-> Lady's Yukata (Redfly)
|
||||
AddItem(08533, 08536); // Lord's Yukata (Whiteflame) <-> Lady's Yukata (Bluefly)
|
||||
AddItem(08534, 08537); // Lord's Yukata (Blueflame) <-> Lady's Yukata (Pinkfly)
|
||||
AddItem(08542, 08549); // Ti Leaf Lei <-> Coronal Summer Halter
|
||||
AddItem(08543, 08550); // Red Summer Maro <-> Red Summer Pareo
|
||||
AddItem(08544, 08551); // South Seas Talisman <-> Sea Breeze Summer Halter
|
||||
AddItem(08545, 08552); // Blue Summer Maro <-> Sea Breeze Summer Pareo
|
||||
AddItem(08546, 08553); // Coeurl Talisman <-> Coeurl Beach Halter
|
||||
AddItem(08547, 08554); // Coeurl Beach Maro <-> Coeurl Beach Pareo
|
||||
AddItem(08548, 08555); // Coeurl Beach Briefs <-> Coeurl Beach Tanga
|
||||
AddItem(10316, 10317); // Southern Seas Vest <-> Southern Seas Swimsuit
|
||||
AddItem(10318, 10319); // Southern Seas Trunks <-> Southern Seas Tanga
|
||||
AddItem(10320, 10321); // Striped Southern Seas Vest <-> Striped Southern Seas Swimsuit
|
||||
AddItem(13298, 13567); // Black-feathered Flat Hat <-> Red-feathered Flat Hat
|
||||
AddItem(13300, 13639); // Lord's Suikan <-> Lady's Suikan
|
||||
AddItem(13724, 13725); // Little Lord's Clogs <-> Little Lady's Clogs
|
||||
AddItem(14854, 14857); // Eastern Lord's Togi <-> Eastern Lady's Togi
|
||||
AddItem(14855, 14858); // Eastern Lord's Trousers <-> Eastern Lady's Loincloth
|
||||
AddItem(14856, 14859); // Eastern Lord's Crakows <-> Eastern Lady's Crakows
|
||||
AddItem(15639, 15642); // Far Eastern Patriarch's Hat <-> Far Eastern Matriarch's Sun Hat
|
||||
AddItem(15640, 15643); // Far Eastern Patriarch's Tunic <-> Far Eastern Matriarch's Dress
|
||||
AddItem(15641, 15644); // Far Eastern Patriarch's Longboots <-> Far Eastern Matriarch's Boots
|
||||
AddItem(15922, 15925); // Moonfire Vest <-> Moonfire Halter
|
||||
AddItem(15923, 15926); // Moonfire Trunks <-> Moonfire Tanga
|
||||
AddItem(15924, 15927); // Moonfire Caligae <-> Moonfire Sandals
|
||||
AddItem(16106, 16111); // Makai Mauler's Facemask <-> Makai Manhandler's Facemask
|
||||
AddItem(16107, 16112); // Makai Mauler's Oilskin <-> Makai Manhandler's Jerkin
|
||||
AddItem(16108, 16113); // Makai Mauler's Fingerless Gloves <-> Makai Manhandler's Fingerless Gloves
|
||||
AddItem(16109, 16114); // Makai Mauler's Leggings <-> Makai Manhandler's Quartertights
|
||||
AddItem(16110, 16115); // Makai Mauler's Boots <-> Makai Manhandler's Longboots
|
||||
AddItem(16116, 16121); // Makai Marksman's Eyepatch <-> Makai Markswoman's Ribbon
|
||||
AddItem(16117, 16122); // Makai Marksman's Battlegarb <-> Makai Markswoman's Battledress
|
||||
AddItem(16118, 16123); // Makai Marksman's Fingerless Gloves <-> Makai Markswoman's Fingerless Gloves
|
||||
AddItem(16119, 16124); // Makai Marksman's Slops <-> Makai Markswoman's Quartertights
|
||||
AddItem(16120, 16125); // Makai Marksman's Boots <-> Makai Markswoman's Longboots
|
||||
AddItem(16126, 16131); // Makai Sun Guide's Circlet <-> Makai Moon Guide's Circlet
|
||||
AddItem(16127, 16132); // Makai Sun Guide's Oilskin <-> Makai Moon Guide's Gown
|
||||
AddItem(16128, 16133); // Makai Sun Guide's Fingerless Gloves <-> Makai Moon Guide's Fingerless Gloves
|
||||
AddItem(16129, 16134); // Makai Sun Guide's Slops <-> Makai Moon Guide's Quartertights
|
||||
AddItem(16130, 16135); // Makai Sun Guide's Boots <-> Makai Moon Guide's Longboots
|
||||
AddItem(16136, 16141); // Makai Priest's Coronet <-> Makai Priestess's Headdress
|
||||
AddItem(16137, 16142); // Makai Priest's Doublet Robe <-> Makai Priestess's Jerkin
|
||||
AddItem(16138, 16143); // Makai Priest's Fingerless Gloves <-> Makai Priestess's Fingerless Gloves
|
||||
AddItem(16139, 16144); // Makai Priest's Slops <-> Makai Priestess's Skirt
|
||||
AddItem(16140, 16145); // Makai Priest's Boots <-> Makai Priestess's Longboots
|
||||
AddItem(16588, 16592); // Far Eastern Gentleman's Hat <-> Far Eastern Beauty's Hairpin
|
||||
AddItem(16589, 16593); // Far Eastern Gentleman's Robe <-> Far Eastern Beauty's Robe
|
||||
AddItem(16590, 16594); // Far Eastern Gentleman's Haidate <-> Far Eastern Beauty's Koshita
|
||||
AddItem(16591, 16595); // Far Eastern Gentleman's Boots <-> Far Eastern Beauty's Boots
|
||||
AddItem(17204, 17209); // Common Makai Mauler's Facemask <-> Common Makai Manhandler's Facemask
|
||||
AddItem(17205, 17210); // Common Makai Mauler's Oilskin <-> Common Makai Manhandler's Jerkin
|
||||
AddItem(17206, 17211); // Common Makai Mauler's Fingerless Gloves <-> Common Makai Manhandler's Fingerless Glove
|
||||
AddItem(17207, 17212); // Common Makai Mauler's Leggings <-> Common Makai Manhandler's Quartertights
|
||||
AddItem(17208, 17213); // Common Makai Mauler's Boots <-> Common Makai Manhandler's Longboots
|
||||
AddItem(17214, 17219); // Common Makai Marksman's Eyepatch <-> Common Makai Markswoman's Ribbon
|
||||
AddItem(17215, 17220); // Common Makai Marksman's Battlegarb <-> Common Makai Markswoman's Battledress
|
||||
AddItem(17216, 17221); // Common Makai Marksman's Fingerless Gloves <-> Common Makai Markswoman's Fingerless Glove
|
||||
AddItem(17217, 17222); // Common Makai Marksman's Slops <-> Common Makai Markswoman's Quartertights
|
||||
AddItem(17218, 17223); // Common Makai Marksman's Boots <-> Common Makai Markswoman's Longboots
|
||||
AddItem(17224, 17229); // Common Makai Sun Guide's Circlet <-> Common Makai Moon Guide's Circlet
|
||||
AddItem(17225, 17230); // Common Makai Sun Guide's Oilskin <-> Common Makai Moon Guide's Gown
|
||||
AddItem(17226, 17231); // Common Makai Sun Guide's Fingerless Gloves <-> Common Makai Moon Guide's Fingerless Glove
|
||||
AddItem(17227, 17232); // Common Makai Sun Guide's Slops <-> Common Makai Moon Guide's Quartertights
|
||||
AddItem(17228, 17233); // Common Makai Sun Guide's Boots <-> Common Makai Moon Guide's Longboots
|
||||
AddItem(17234, 17239); // Common Makai Priest's Coronet <-> Common Makai Priestess's Headdress
|
||||
AddItem(17235, 17240); // Common Makai Priest's Doublet Robe <-> Common Makai Priestess's Jerkin
|
||||
AddItem(17236, 17241); // Common Makai Priest's Fingerless Gloves <-> Common Makai Priestess's Fingerless Gloves
|
||||
AddItem(17237, 17242); // Common Makai Priest's Slops <-> Common Makai Priestess's Skirt
|
||||
AddItem(17238, 17243); // Common Makai Priest's Boots <-> Common Makai Priestess's Longboots
|
||||
AddItem(17481, 17476); // Royal Seneschal's Chapeau <-> Songbird Hat
|
||||
AddItem(17482, 17477); // Royal Seneschal's Coat <-> Songbird Jacket
|
||||
AddItem(17483, 17478); // Royal Seneschal's Fingerless Gloves <-> Songbird Gloves
|
||||
AddItem(17484, 17479); // Royal Seneschal's Breeches <-> Songbird Skirt
|
||||
AddItem(17485, 17480); // Royal Seneschal's Boots <-> Songbird Boots
|
||||
AddItem(20479, 20484); // Star of the Nezha Lord <-> Star of the Nezha Lady
|
||||
AddItem(20480, 20485); // Nezha Lord's Togi <-> Nezha Lady's Togi
|
||||
AddItem(20481, 20486); // Nezha Lord's Gloves <-> Nezha Lady's Gloves
|
||||
AddItem(20482, 20487); // Nezha Lord's Slops <-> Nezha Lady's Slops
|
||||
AddItem(20483, 20488); // Nezha Lord's Boots <-> Nezha Lady's Kneeboots
|
||||
AddItem(22367, 22372); // Faerie Tale Prince's Circlet <-> Faerie Tale Princess's Tiara
|
||||
AddItem(22368, 22373); // Faerie Tale Prince's Vest <-> Faerie Tale Princess's Dress
|
||||
AddItem(22369, 22374); // Faerie Tale Prince's Gloves <-> Faerie Tale Princess's Gloves
|
||||
AddItem(22370, 22375); // Faerie Tale Prince's Slops <-> Faerie Tale Princess's Long Skirt
|
||||
AddItem(22371, 22376); // Faerie Tale Prince's Boots <-> Faerie Tale Princess's Heels
|
||||
AddItem(24599, 24602); // Far Eastern Schoolboy's Hat <-> Far Eastern Schoolgirl's Hair Ribbon
|
||||
AddItem(24600, 24603); // Far Eastern Schoolboy's Hakama <-> Far Eastern Schoolgirl's Hakama
|
||||
AddItem(24601, 24604); // Far Eastern Schoolboy's Zori <-> Far Eastern Schoolgirl's Boots
|
||||
AddItem(28558, 28573); // Valentione Rose Hat <-> Valentione Rose Ribboned Hat
|
||||
AddItem(28559, 28574); // Valentione Rose Waistcoat <-> Valentione Rose Dress
|
||||
AddItem(28560, 28575); // Valentione Rose Gloves <-> Valentione Rose Ribboned Gloves
|
||||
AddItem(28561, 28576); // Valentione Rose Slacks <-> Valentione Rose Tights
|
||||
AddItem(28562, 28577); // Valentione Rose Shoes <-> Valentione Rose Heels
|
||||
AddItem(28563, 28578); // Valentione Forget-me-not Hat <-> Valentione Forget-me-not Ribboned Hat
|
||||
AddItem(28564, 28579); // Valentione Forget-me-not Waistcoat <-> Valentione Forget-me-not Dress
|
||||
AddItem(28565, 28580); // Valentione Forget-me-not Gloves <-> Valentione Forget-me-not Ribboned Gloves
|
||||
AddItem(28566, 28581); // Valentione Forget-me-not Slacks <-> Valentione Forget-me-not Tights
|
||||
AddItem(28567, 28582); // Valentione Forget-me-not Shoes <-> Valentione Forget-me-not Heels
|
||||
AddItem(28568, 28583); // Valentione Acacia Hat <-> Valentione Acacia Ribboned Hat
|
||||
AddItem(28569, 28584); // Valentione Acacia Waistcoat <-> Valentione Acacia Dress
|
||||
AddItem(28570, 28585); // Valentione Acacia Gloves <-> Valentione Acacia Ribboned Gloves
|
||||
AddItem(28571, 28586); // Valentione Acacia Slacks <-> Valentione Acacia Tights
|
||||
AddItem(28572, 28587); // Valentione Acacia Shoes <-> Valentione Acacia Heels
|
||||
AddItem(28600, 28605); // Eastern Lord Errant's Hat <-> Eastern Lady Errant's Hat
|
||||
AddItem(28601, 28606); // Eastern Lord Errant's Jacket <-> Eastern Lady Errant's Coat
|
||||
AddItem(28602, 28607); // Eastern Lord Errant's Wristbands <-> Eastern Lady Errant's Gloves
|
||||
AddItem(28603, 28608); // Eastern Lord Errant's Trousers <-> Eastern Lady Errant's Skirt
|
||||
AddItem(28604, 28609); // Eastern Lord Errant's Shoes <-> Eastern Lady Errant's Boots
|
||||
AddItem(31408, 31413); // Bergsteiger's Hat <-> Dirndl's Hat
|
||||
AddItem(31409, 31414); // Bergsteiger's Jacket <-> Dirndl's Bodice
|
||||
AddItem(31410, 31415); // Bergsteiger's Halfgloves <-> Dirndl's Wrist Torque
|
||||
AddItem(31411, 31416); // Bergsteiger's Halfslops <-> Dirndl's Long Skirt
|
||||
AddItem(31412, 31417); // Bergsteiger's Boots <-> Dirndl's Pumps
|
||||
AddItem(36336, 36337); // Omega-M Attire <-> Omega-F Attire
|
||||
AddItem(36338, 36339); // Omega-M Ear Cuffs <-> Omega-F Earrings
|
||||
AddItem(37442, 37447); // Makai Vanguard's Monocle <-> Makai Vanbreaker's Ribbon
|
||||
AddItem(37443, 37448); // Makai Vanguard's Battlegarb <-> Makai Vanbreaker's Battledress
|
||||
AddItem(37444, 37449); // Makai Vanguard's Fingerless Gloves <-> Makai Vanbreaker's Fingerless Gloves
|
||||
AddItem(37445, 37450); // Makai Vanguard's Leggings <-> Makai Vanbreaker's Quartertights
|
||||
AddItem(37446, 37451); // Makai Vanguard's Boots <-> Makai Vanbreaker's Longboots
|
||||
AddItem(37452, 37457); // Makai Harbinger's Facemask <-> Makai Harrower's Facemask
|
||||
AddItem(37453, 37458); // Makai Harbinger's Battlegarb <-> Makai Harrower's Jerkin
|
||||
AddItem(37454, 37459); // Makai Harbinger's Fingerless Gloves <-> Makai Harrower's Fingerless Gloves
|
||||
AddItem(37455, 37460); // Makai Harbinger's Leggings <-> Makai Harrower's Quartertights
|
||||
AddItem(37456, 37461); // Makai Harbinger's Boots <-> Makai Harrower's Longboots
|
||||
AddItem(37462, 37467); // Common Makai Vanguard's Monocle <-> Common Makai Vanbreaker's Ribbon
|
||||
AddItem(37463, 37468); // Common Makai Vanguard's Battlegarb <-> Common Makai Vanbreaker's Battledress
|
||||
AddItem(37464, 37469); // Common Makai Vanguard's Fingerless Gloves <-> Common Makai Vanbreaker's Fingerless Gloves
|
||||
AddItem(37465, 37470); // Common Makai Vanguard's Leggings <-> Common Makai Vanbreaker's Quartertights
|
||||
AddItem(37466, 37471); // Common Makai Vanguard's Boots <-> Common Makai Vanbreaker's Longboots
|
||||
AddItem(37472, 37477); // Common Makai Harbinger's Facemask <-> Common Makai Harrower's Facemask
|
||||
AddItem(37473, 37478); // Common Makai Harbinger's Battlegarb <-> Common Makai Harrower's Jerkin
|
||||
AddItem(37474, 37479); // Common Makai Harbinger's Fingerless Gloves <-> Common Makai Harrower's Fingerless Gloves
|
||||
AddItem(37475, 37480); // Common Makai Harbinger's Leggings <-> Common Makai Harrower's Quartertights
|
||||
AddItem(37476, 37481); // Common Makai Harbinger's Boots <-> Common Makai Harrower's Longboots
|
||||
AddItem(23003, 23008); // Mun'gaek Hat <-> Eastern Socialite's Hat
|
||||
AddItem(23004, 23009); // Mun'gaek Uibok <-> Eastern Socialite's Cheongsam
|
||||
AddItem(23005, 23010); // Mun'gaek Cuffs <-> Eastern Socialite's Gloves
|
||||
AddItem(23006, 23011); // Mun'gaek Trousers <-> Eastern Socialite's Skirt
|
||||
AddItem(23007, 23012); // Mun'gaek Boots <-> Eastern Socialite's Boots
|
||||
AddItem(24148, 24153); // Far Eastern Officer's Hat <-> Far Eastern Maiden's Hat
|
||||
AddItem(24149, 24154); // Far Eastern Officer's Robe <-> Far Eastern Maiden's Tunic
|
||||
AddItem(24150, 24155); // Far Eastern Officer's Armband <-> Far Eastern Maiden's Armband
|
||||
AddItem(24151, 24156); // Far Eastern Officer's Bottoms <-> Far Eastern Maiden's Bottoms
|
||||
AddItem(24152, 24157); // Far Eastern Officer's Boots <-> Far Eastern Maiden's Boots
|
||||
AddItem(13323, 13322); // Scion Thief's Tunic <-> Scion Conjurer's Dalmatica
|
||||
AddItem(13693, 10034, true, false); // Scion Thief's Halfgloves -> The Emperor's New Gloves
|
||||
AddItem(13694, 13691); // Scion Thief's Gaskins <-> Scion Conjurer's Chausses
|
||||
AddItem(13695, 13692); // Scion Thief's Armored Caligae <-> Scion Conjurer's Pattens
|
||||
AddItem(13326, 30063); // Scion Thaumaturge's Robe <-> Scion Sorceress's Headdress
|
||||
AddItem(13696, 30062); // Scion Thaumaturge's Monocle <-> Scion Sorceress's Robe
|
||||
AddItem(13697, 30064); // Scion Thaumaturge's Gauntlets <-> Scion Sorceress's Shadowtalons
|
||||
AddItem(13698, 10035, true, false); // Scion Thaumaturge's Gaskins -> The Emperor's New Breeches
|
||||
AddItem(13699, 30065); // Scion Thaumaturge's Moccasins <-> Scion Sorceress's High Boots
|
||||
AddItem(13327, 15942); // Scion Chronocler's Cowl <-> Scion Healer's Robe
|
||||
AddItem(13700, 10034, true, false); // Scion Chronocler's Ringbands -> The Emperor's New Gloves
|
||||
AddItem(13701, 15943); // Scion Chronocler's Tights <-> Scion Healer's Halftights
|
||||
AddItem(13702, 15944); // Scion Chronocler's Caligae <-> Scion Healer's Highboots
|
||||
AddItem(14861, 13324); // Head Engineer's Goggles <-> Scion Striker's Visor
|
||||
AddItem(14862, 13325); // Head Engineer's Attire <-> Scion Striker's Attire
|
||||
AddItem(15938, 33751); // Scion Rogue's Jacket <-> Oracle Top
|
||||
AddItem(15939, 10034, true, false); // Scion Rogue's Armguards -> The Emperor's New Gloves
|
||||
AddItem(15940, 33752); // Scion Rogue's Gaskins <-> Oracle Leggings
|
||||
AddItem(15941, 33753); // Scion Rogue's Boots <-> Oracle Pantalettes
|
||||
AddItem(16042, 16046); // Abes Jacket <-> High Summoner's Dress
|
||||
AddItem(16043, 16047); // Abes Gloves <-> High Summoner's Armlets
|
||||
AddItem(16044, 10035, true, false); // Abes Halfslops -> The Emperor's New Breeches
|
||||
AddItem(16045, 16048); // Abes Boots <-> High Summoner's Boots
|
||||
AddItem(17473, 28553); // Lord Commander's Coat <-> Majestic Dress
|
||||
AddItem(17474, 28554); // Lord Commander's Gloves <-> Majestic Wristdresses
|
||||
AddItem(10036, 28555, false); // Emperor's New Boots <- Majestic Boots
|
||||
AddItem(21021, 21026); // Werewolf Feet <-> Werewolf Legs
|
||||
AddItem(22452, 20633); // Cracked Manderville Monocle <-> Blackbosom Hat
|
||||
AddItem(22453, 20634); // Torn Manderville Coatee <-> Blackbosom Dress
|
||||
AddItem(22454, 20635); // Singed Manderville Gloves <-> Blackbosom Dress Gloves
|
||||
AddItem(22455, 10035, true, false); // Stained Manderville Bottoms -> The Emperor's New Breeches
|
||||
AddItem(22456, 20636); // Scuffed Manderville Gaiters <-> lackbosom Boots
|
||||
AddItem(23013, 21302); // Doman Liege's Dogi <-> Scion Liberator's Jacket
|
||||
AddItem(23014, 21303); // Doman Liege's Kote <-> Scion Liberator's Fingerless Gloves
|
||||
AddItem(23015, 21304); // Doman Liege's Kyakui <-> Scion Liberator's Pantalettes
|
||||
AddItem(23016, 21305); // Doman Liege's Kyahan <-> Scion Liberator's Sabatons
|
||||
AddItem(09293, 21306, false); // The Emperor's New Earrings <- Scion Liberator's Earrings
|
||||
AddItem(24158, 23008); // Leal Samurai's Kasa <-> Eastern Socialite's Hat
|
||||
AddItem(24159, 23009); // Leal Samurai's Dogi <-> Eastern Socialite's Cheongsam
|
||||
AddItem(24160, 23010); // Leal Samurai's Tekko <-> Eastern Socialite's Gloves
|
||||
AddItem(24161, 23011); // Leal Samurai's Tsutsu-hakama <-> Eastern Socialite's Skirt
|
||||
AddItem(24162, 23012); // Leal Samurai's Geta <-> Eastern Socialite's Boots
|
||||
AddItem(02966, 13321, false); // Reindeer Suit <- Antecedent's Attire
|
||||
AddItem(15479, 36843, false); // Swine Body <- Lyse's Leadership Attire
|
||||
AddItem(21941, 24999, false); // Ala Mhigan Gown <- Gown of Light
|
||||
AddItem(30757, 25000, false); // Southern Seas Skirt <- Skirt of Light
|
||||
AddItem(36821, 27933, false); // Archfiend Helm <- Scion Hearer's Hood
|
||||
AddItem(36822, 27934, false); // Archfiend Armor <- Scion Hearer's Coat
|
||||
AddItem(36825, 27935, false); // Archfiend Sabatons <- Scion Hearer's Shoes
|
||||
}
|
||||
|
||||
// The racial starter sets are available for all 4 slots each,
|
||||
// but have no associated accessories or hats.
|
||||
private static readonly uint[] RaceGenderGroup =
|
||||
{
|
||||
0x020054,
|
||||
0x020055,
|
||||
0x020056,
|
||||
0x020057,
|
||||
0x02005C,
|
||||
0x02005D,
|
||||
0x020058,
|
||||
0x020059,
|
||||
0x02005A,
|
||||
0x02005B,
|
||||
0x020101,
|
||||
0x020102,
|
||||
0x010255,
|
||||
uint.MaxValue, // TODO: Female Hrothgar
|
||||
0x0102E8,
|
||||
0x010245,
|
||||
};
|
||||
}
|
||||
|
|
@ -3,23 +3,25 @@ using Penumbra.GameData.Enums;
|
|||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
|
||||
// Turn EquipSlot into a bitfield flag enum.
|
||||
[Flags]
|
||||
public enum CharacterEquipMask : ushort
|
||||
{
|
||||
None = 0,
|
||||
None = 0,
|
||||
MainHand = 0b000000000001,
|
||||
OffHand = 0b000000000010,
|
||||
Head = 0b000000000100,
|
||||
Body = 0b000000001000,
|
||||
Hands = 0b000000010000,
|
||||
Legs = 0b000000100000,
|
||||
Feet = 0b000001000000,
|
||||
Ears = 0b000010000000,
|
||||
Neck = 0b000100000000,
|
||||
Wrists = 0b001000000000,
|
||||
RFinger = 0b010000000000,
|
||||
LFinger = 0b100000000000,
|
||||
All = 0b111111111111,
|
||||
OffHand = 0b000000000010,
|
||||
Head = 0b000000000100,
|
||||
Body = 0b000000001000,
|
||||
Hands = 0b000000010000,
|
||||
Legs = 0b000000100000,
|
||||
Feet = 0b000001000000,
|
||||
Ears = 0b000010000000,
|
||||
Neck = 0b000100000000,
|
||||
Wrists = 0b001000000000,
|
||||
RFinger = 0b010000000000,
|
||||
LFinger = 0b100000000000,
|
||||
All = 0b111111111111,
|
||||
}
|
||||
|
||||
public static class CharacterEquipMaskExtensions
|
||||
|
|
@ -28,16 +30,16 @@ public static class CharacterEquipMaskExtensions
|
|||
=> slot switch
|
||||
{
|
||||
EquipSlot.Unknown => false,
|
||||
EquipSlot.Head => mask.HasFlag(CharacterEquipMask.Head),
|
||||
EquipSlot.Body => mask.HasFlag(CharacterEquipMask.Body),
|
||||
EquipSlot.Hands => mask.HasFlag(CharacterEquipMask.Hands),
|
||||
EquipSlot.Legs => mask.HasFlag(CharacterEquipMask.Legs),
|
||||
EquipSlot.Feet => mask.HasFlag(CharacterEquipMask.Feet),
|
||||
EquipSlot.Ears => mask.HasFlag(CharacterEquipMask.Ears),
|
||||
EquipSlot.Neck => mask.HasFlag(CharacterEquipMask.Neck),
|
||||
EquipSlot.Wrists => mask.HasFlag(CharacterEquipMask.Wrists),
|
||||
EquipSlot.Head => mask.HasFlag(CharacterEquipMask.Head),
|
||||
EquipSlot.Body => mask.HasFlag(CharacterEquipMask.Body),
|
||||
EquipSlot.Hands => mask.HasFlag(CharacterEquipMask.Hands),
|
||||
EquipSlot.Legs => mask.HasFlag(CharacterEquipMask.Legs),
|
||||
EquipSlot.Feet => mask.HasFlag(CharacterEquipMask.Feet),
|
||||
EquipSlot.Ears => mask.HasFlag(CharacterEquipMask.Ears),
|
||||
EquipSlot.Neck => mask.HasFlag(CharacterEquipMask.Neck),
|
||||
EquipSlot.Wrists => mask.HasFlag(CharacterEquipMask.Wrists),
|
||||
EquipSlot.RFinger => mask.HasFlag(CharacterEquipMask.RFinger),
|
||||
EquipSlot.LFinger => mask.HasFlag(CharacterEquipMask.LFinger),
|
||||
_ => false,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,37 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
// An Item wrapper struct that contains the item table, a precomputed name and the associated equip slot.
|
||||
public readonly struct Item
|
||||
{
|
||||
public readonly Lumina.Excel.GeneratedSheets.Item Base;
|
||||
public readonly string Name;
|
||||
public readonly EquipSlot EquippableTo;
|
||||
|
||||
// Obtain the main model info used by the item.
|
||||
public (SetId id, WeaponType type, ushort variant) MainModel
|
||||
=> ParseModel(EquippableTo, Base.ModelMain);
|
||||
|
||||
// Obtain the sub model info used by the item. Will be 0 if the item has no sub model.
|
||||
public (SetId id, WeaponType type, ushort variant) SubModel
|
||||
=> ParseModel(EquippableTo, Base.ModelSub);
|
||||
|
||||
public bool HasSubModel
|
||||
=> Base.ModelSub != 0;
|
||||
|
||||
// Create a new item from its sheet list with the given name and either the inferred equip slot or the given one.
|
||||
public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)
|
||||
{
|
||||
Base = item;
|
||||
Name = name;
|
||||
EquippableTo = slot == EquipSlot.Unknown ? ((EquipSlot)item.EquipSlotCategory.Row).ToSlot() : slot;
|
||||
}
|
||||
|
||||
// Create empty Nothing items.
|
||||
public static Item Nothing(EquipSlot slot)
|
||||
=> new("Nothing", slot);
|
||||
|
||||
// Produce the relevant model information for a given item and equip slot.
|
||||
private static (SetId id, WeaponType type, ushort variant) ParseModel(EquipSlot slot, ulong data)
|
||||
{
|
||||
if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
|
||||
|
|
@ -22,33 +51,14 @@ public readonly struct Item
|
|||
}
|
||||
}
|
||||
|
||||
public readonly Lumina.Excel.GeneratedSheets.Item Base;
|
||||
public readonly string Name;
|
||||
public readonly EquipSlot EquippableTo;
|
||||
|
||||
public (SetId id, WeaponType type, ushort variant) MainModel
|
||||
=> ParseModel(EquippableTo, Base.ModelMain);
|
||||
|
||||
public bool HasSubModel
|
||||
=> Base.ModelSub != 0;
|
||||
|
||||
public (SetId id, WeaponType type, ushort variant) SubModel
|
||||
=> ParseModel(EquippableTo, Base.ModelSub);
|
||||
|
||||
public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)
|
||||
{
|
||||
Base = item;
|
||||
Name = name;
|
||||
EquippableTo = slot == EquipSlot.Unknown ? ((EquipSlot)item.EquipSlotCategory.Row).ToSlot() : slot;
|
||||
}
|
||||
|
||||
public static Item Nothing(EquipSlot slot)
|
||||
=> new("Nothing", slot);
|
||||
|
||||
// Used for 'Nothing' items.
|
||||
private Item(string name, EquipSlot slot)
|
||||
{
|
||||
Name = name;
|
||||
Base = new Lumina.Excel.GeneratedSheets.Item();
|
||||
EquippableTo = slot;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
// A struct containing the different jobs the game supports.
|
||||
// Also contains the jobs Name and Abbreviation as strings.
|
||||
public readonly struct Job
|
||||
{
|
||||
public readonly string Name;
|
||||
|
|
@ -14,7 +17,10 @@ public readonly struct Job
|
|||
public Job(ClassJob job)
|
||||
{
|
||||
Base = job;
|
||||
Name = job.Name.ToString();
|
||||
Abbreviation = job.Abbreviation.ToString();
|
||||
Name = job.Name.ToDalamudString().ToString();
|
||||
Abbreviation = job.Abbreviation.ToDalamudString().ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,31 @@
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
// The game specifies different job groups that can contain specific jobs or not.
|
||||
public readonly struct JobGroup
|
||||
{
|
||||
public readonly string Name;
|
||||
private readonly ulong _flags;
|
||||
public readonly int Count;
|
||||
public readonly uint Id;
|
||||
public readonly string Name;
|
||||
public readonly int Count;
|
||||
public readonly uint Id;
|
||||
private readonly ulong _flags;
|
||||
|
||||
// Create a job group from a given category and the ClassJob sheet.
|
||||
// It looks up the different jobs contained in the category and sets the flags appropriately.
|
||||
public JobGroup(ClassJobCategory group, ExcelSheet<ClassJob> jobs)
|
||||
{
|
||||
Count = 0;
|
||||
Count = 0;
|
||||
_flags = 0ul;
|
||||
Id = group.RowId;
|
||||
Name = group.Name.ToString();
|
||||
Id = group.RowId;
|
||||
Name = group.Name.ToString();
|
||||
|
||||
Debug.Assert(jobs.RowCount < 64);
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
var abbr = job.Abbreviation.ToString();
|
||||
if (!abbr.Any())
|
||||
if (abbr.Length == 0)
|
||||
continue;
|
||||
|
||||
var prop = group.GetType().GetProperty(abbr);
|
||||
|
|
@ -37,9 +39,11 @@ public readonly struct JobGroup
|
|||
}
|
||||
}
|
||||
|
||||
// Check if a job is contained inside this group.
|
||||
public bool Fits(Job job)
|
||||
=> Fits(job.Id);
|
||||
|
||||
// Check if a job is contained inside this group.
|
||||
public bool Fits(uint jobId)
|
||||
{
|
||||
var flag = 1ul << (int)jobId;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,28 @@
|
|||
using Penumbra.GameData.Structs;
|
||||
using Dalamud.Utility;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
// A wrapper for the clothing dyes the game provides with their RGBA color value, game ID, unmodified color value and name.
|
||||
public readonly struct Stain
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly uint RgbaColor;
|
||||
// An empty stain with transparent color.
|
||||
public static readonly Stain None = new("None");
|
||||
|
||||
public readonly string Name;
|
||||
public readonly uint RgbaColor;
|
||||
|
||||
// Combine the Id byte with the 3 bytes of color values.
|
||||
private readonly uint _seColorId;
|
||||
|
||||
public byte R
|
||||
=> (byte)(RgbaColor & 0xFF);
|
||||
|
||||
public byte G
|
||||
=> (byte)(RgbaColor >> 8 & 0xFF);
|
||||
=> (byte)((RgbaColor >> 8) & 0xFF);
|
||||
|
||||
public byte B
|
||||
=> (byte)(RgbaColor >> 16 & 0xFF);
|
||||
=> (byte)((RgbaColor >> 16) & 0xFF);
|
||||
|
||||
public byte Intensity
|
||||
=> (byte)((1 + R + G + B) / 3);
|
||||
|
|
@ -27,23 +33,22 @@ public readonly struct Stain
|
|||
public StainId RowIndex
|
||||
=> (StainId)(_seColorId >> 24);
|
||||
|
||||
|
||||
// R and B need to be shuffled and Alpha set to max.
|
||||
public static uint SeColorToRgba(uint color)
|
||||
=> (color & 0xFF) << 16 | color >> 16 & 0xFF | color & 0xFF00 | 0xFF000000;
|
||||
=> ((color & 0xFF) << 16) | ((color >> 16) & 0xFF) | (color & 0xFF00) | 0xFF000000;
|
||||
|
||||
public Stain(byte index, Lumina.Excel.GeneratedSheets.Stain stain)
|
||||
{
|
||||
Name = stain.Name.ToString();
|
||||
_seColorId = stain.Color | (uint)index << 24;
|
||||
RgbaColor = SeColorToRgba(stain.Color);
|
||||
Name = stain.Name.ToDalamudString().ToString();
|
||||
_seColorId = stain.Color | ((uint)index << 24);
|
||||
RgbaColor = SeColorToRgba(stain.Color);
|
||||
}
|
||||
|
||||
public static readonly Stain None = new("None");
|
||||
|
||||
// Only used by None.
|
||||
private Stain(string name)
|
||||
{
|
||||
Name = name;
|
||||
Name = name;
|
||||
_seColorId = 0;
|
||||
RgbaColor = 0;
|
||||
RgbaColor = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.GameData", "Glamo
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj", "{9BEE2336-AA93-4669-8EEA-4756B3B2D024}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.PlayerWatch", "..\Penumbra\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj", "{FECEDB39-C103-4333-82A6-A422BDC51EEE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "..\Penumbra\OtterGui\OtterGui.csproj", "{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}"
|
||||
EndProject
|
||||
Global
|
||||
|
|
@ -65,18 +63,6 @@ Global
|
|||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|x86.Build.0 = Release|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x64.Build.0 = Release|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
|
|
|||
|
|
@ -53,270 +53,269 @@ public class GlamourerIpc : IDisposable
|
|||
|
||||
private void DisposeProviders()
|
||||
{
|
||||
ProviderGetAllCustomization?.UnregisterFunc();
|
||||
ProviderGetAllCustomizationFromCharacter?.UnregisterFunc();
|
||||
ProviderApplyAll?.UnregisterAction();
|
||||
ProviderApplyAllToCharacter?.UnregisterAction();
|
||||
ProviderApplyOnlyCustomization?.UnregisterAction();
|
||||
ProviderApplyOnlyCustomizationToCharacter?.UnregisterAction();
|
||||
ProviderApplyOnlyEquipment?.UnregisterAction();
|
||||
ProviderApplyOnlyEquipmentToCharacter?.UnregisterAction();
|
||||
ProviderRevert?.UnregisterAction();
|
||||
ProviderRevertCharacter?.UnregisterAction();
|
||||
ProviderGetApiVersion?.UnregisterFunc();
|
||||
|
||||
// ProviderGetAllCustomization?.UnregisterFunc();
|
||||
// ProviderGetAllCustomizationFromCharacter?.UnregisterFunc();
|
||||
// ProviderApplyAll?.UnregisterAction();
|
||||
// ProviderApplyAllToCharacter?.UnregisterAction();
|
||||
// ProviderApplyOnlyCustomization?.UnregisterAction();
|
||||
// ProviderApplyOnlyCustomizationToCharacter?.UnregisterAction();
|
||||
// ProviderApplyOnlyEquipment?.UnregisterAction();
|
||||
// ProviderApplyOnlyEquipmentToCharacter?.UnregisterAction();
|
||||
// ProviderRevert?.UnregisterAction();
|
||||
// ProviderRevertCharacter?.UnregisterAction();
|
||||
// ProviderGetApiVersion?.UnregisterFunc();
|
||||
}
|
||||
|
||||
private void InitializeProviders()
|
||||
{
|
||||
try
|
||||
{
|
||||
ProviderGetApiVersion = _pluginInterface.GetIpcProvider<int>(LabelProviderApiVersion);
|
||||
ProviderGetApiVersion.RegisterFunc(GetApiVersion);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderGetAllCustomization = _pluginInterface.GetIpcProvider<string, string?>(LabelProviderGetAllCustomization);
|
||||
ProviderGetAllCustomization.RegisterFunc(GetAllCustomization);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderGetAllCustomizationFromCharacter = _pluginInterface.GetIpcProvider<Character?, string?>(LabelProviderGetAllCustomizationFromCharacter);
|
||||
ProviderGetAllCustomizationFromCharacter.RegisterFunc(GetAllCustomization);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderGetAllCustomizationFromCharacter}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderApplyAll =
|
||||
_pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyAll);
|
||||
ProviderApplyAll.RegisterAction(ApplyAll);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderApplyAllToCharacter =
|
||||
_pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyAllToCharacter);
|
||||
ProviderApplyAllToCharacter.RegisterAction(ApplyAll);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderApplyOnlyCustomization =
|
||||
_pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyOnlyCustomization);
|
||||
ProviderApplyOnlyCustomization.RegisterAction(ApplyOnlyCustomization);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderApplyOnlyCustomizationToCharacter =
|
||||
_pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyOnlyCustomizationToCharacter);
|
||||
ProviderApplyOnlyCustomizationToCharacter.RegisterAction(ApplyOnlyCustomization);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderApplyOnlyEquipment =
|
||||
_pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyOnlyEquipment);
|
||||
ProviderApplyOnlyEquipment.RegisterAction(ApplyOnlyEquipment);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderApplyOnlyEquipmentToCharacter =
|
||||
_pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyOnlyEquipmentToCharacter);
|
||||
ProviderApplyOnlyEquipmentToCharacter.RegisterAction(ApplyOnlyEquipment);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderRevert =
|
||||
_pluginInterface.GetIpcProvider<string, object>(LabelProviderRevert);
|
||||
ProviderRevert.RegisterAction(Revert);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderRevertCharacter =
|
||||
_pluginInterface.GetIpcProvider<Character?, object>(LabelProviderRevertCharacter);
|
||||
ProviderRevertCharacter.RegisterAction(Revert);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}.");
|
||||
}
|
||||
//try
|
||||
//{
|
||||
// ProviderGetApiVersion = _pluginInterface.GetIpcProvider<int>(LabelProviderApiVersion);
|
||||
// ProviderGetApiVersion.RegisterFunc(GetApiVersion);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}.");
|
||||
//}
|
||||
//
|
||||
//try
|
||||
//{
|
||||
// ProviderGetAllCustomization = _pluginInterface.GetIpcProvider<string, string?>(LabelProviderGetAllCustomization);
|
||||
// ProviderGetAllCustomization.RegisterFunc(GetAllCustomization);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
|
||||
//}
|
||||
//
|
||||
//try
|
||||
//{
|
||||
// ProviderGetAllCustomizationFromCharacter = _pluginInterface.GetIpcProvider<Character?, string?>(LabelProviderGetAllCustomizationFromCharacter);
|
||||
// ProviderGetAllCustomizationFromCharacter.RegisterFunc(GetAllCustomization);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderGetAllCustomizationFromCharacter}.");
|
||||
//}
|
||||
//
|
||||
//try
|
||||
//{
|
||||
// ProviderApplyAll =
|
||||
// _pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyAll);
|
||||
// ProviderApplyAll.RegisterAction(ApplyAll);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}.");
|
||||
//}
|
||||
//
|
||||
//try
|
||||
//{
|
||||
// ProviderApplyAllToCharacter =
|
||||
// _pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyAllToCharacter);
|
||||
// ProviderApplyAllToCharacter.RegisterAction(ApplyAll);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}.");
|
||||
//}
|
||||
//
|
||||
//try
|
||||
//{
|
||||
// ProviderApplyOnlyCustomization =
|
||||
// _pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyOnlyCustomization);
|
||||
// ProviderApplyOnlyCustomization.RegisterAction(ApplyOnlyCustomization);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}.");
|
||||
//}
|
||||
//
|
||||
//try
|
||||
//{
|
||||
// ProviderApplyOnlyCustomizationToCharacter =
|
||||
// _pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyOnlyCustomizationToCharacter);
|
||||
// ProviderApplyOnlyCustomizationToCharacter.RegisterAction(ApplyOnlyCustomization);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}.");
|
||||
//}
|
||||
//
|
||||
//try
|
||||
//{
|
||||
// ProviderApplyOnlyEquipment =
|
||||
// _pluginInterface.GetIpcProvider<string, string, object>(LabelProviderApplyOnlyEquipment);
|
||||
// ProviderApplyOnlyEquipment.RegisterAction(ApplyOnlyEquipment);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
|
||||
//}
|
||||
//
|
||||
//try
|
||||
//{
|
||||
// ProviderApplyOnlyEquipmentToCharacter =
|
||||
// _pluginInterface.GetIpcProvider<string, Character?, object>(LabelProviderApplyOnlyEquipmentToCharacter);
|
||||
// ProviderApplyOnlyEquipmentToCharacter.RegisterAction(ApplyOnlyEquipment);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}.");
|
||||
//}
|
||||
//
|
||||
//try
|
||||
//{
|
||||
// ProviderRevert =
|
||||
// _pluginInterface.GetIpcProvider<string, object>(LabelProviderRevert);
|
||||
// ProviderRevert.RegisterAction(Revert);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}.");
|
||||
//}
|
||||
//
|
||||
//try
|
||||
//{
|
||||
// ProviderRevertCharacter =
|
||||
// _pluginInterface.GetIpcProvider<Character?, object>(LabelProviderRevertCharacter);
|
||||
// ProviderRevertCharacter.RegisterAction(Revert);
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}.");
|
||||
//}
|
||||
}
|
||||
|
||||
private static int GetApiVersion()
|
||||
=> CurrentApiVersion;
|
||||
|
||||
private void ApplyAll(string customization, string characterName)
|
||||
{
|
||||
var save = CharacterSave.FromString(customization);
|
||||
foreach (var gameObject in _objectTable)
|
||||
{
|
||||
if (gameObject.Name.ToString() != characterName)
|
||||
continue;
|
||||
|
||||
var player = (Character)gameObject;
|
||||
Glamourer.RevertableDesigns.Revert(player);
|
||||
save.Apply(player);
|
||||
Glamourer.Penumbra.UpdateCharacters(player, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyAll(string customization, Character? character)
|
||||
{
|
||||
if (character == null)
|
||||
return;
|
||||
var save = CharacterSave.FromString(customization);
|
||||
Glamourer.RevertableDesigns.Revert(character);
|
||||
save.Apply(character);
|
||||
Glamourer.Penumbra.UpdateCharacters(character, null);
|
||||
}
|
||||
|
||||
private void ApplyOnlyCustomization(string customization, string characterName)
|
||||
{
|
||||
var save = CharacterSave.FromString(customization);
|
||||
foreach (var gameObject in _objectTable)
|
||||
{
|
||||
if (gameObject.Name.ToString() != characterName)
|
||||
continue;
|
||||
|
||||
var player = (Character)gameObject;
|
||||
Glamourer.RevertableDesigns.Revert(player);
|
||||
save.ApplyOnlyCustomizations(player);
|
||||
Glamourer.Penumbra.UpdateCharacters(player, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyOnlyCustomization(string customization, Character? character)
|
||||
{
|
||||
if (character == null)
|
||||
return;
|
||||
var save = CharacterSave.FromString(customization);
|
||||
Glamourer.RevertableDesigns.Revert(character);
|
||||
save.ApplyOnlyCustomizations(character);
|
||||
Glamourer.Penumbra.UpdateCharacters(character, null);
|
||||
}
|
||||
|
||||
private void ApplyOnlyEquipment(string customization, string characterName)
|
||||
{
|
||||
var save = CharacterSave.FromString(customization);
|
||||
foreach (var gameObject in _objectTable)
|
||||
{
|
||||
if (gameObject.Name.ToString() != characterName)
|
||||
continue;
|
||||
|
||||
var player = (Character)gameObject;
|
||||
Glamourer.RevertableDesigns.Revert(player);
|
||||
save.ApplyOnlyEquipment(player);
|
||||
Glamourer.Penumbra.UpdateCharacters(player, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyOnlyEquipment(string customization, Character? character)
|
||||
{
|
||||
if (character == null)
|
||||
return;
|
||||
var save = CharacterSave.FromString(customization);
|
||||
Glamourer.RevertableDesigns.Revert(character);
|
||||
save.ApplyOnlyEquipment(character);
|
||||
Glamourer.Penumbra.UpdateCharacters(character, null);
|
||||
}
|
||||
|
||||
private void Revert(string characterName)
|
||||
{
|
||||
foreach (var gameObject in _objectTable)
|
||||
{
|
||||
if (gameObject.Name.ToString() != characterName)
|
||||
continue;
|
||||
|
||||
var player = (Character)gameObject;
|
||||
Glamourer.RevertableDesigns.Revert(player);
|
||||
Glamourer.Penumbra.UpdateCharacters(player, null);
|
||||
return;
|
||||
}
|
||||
|
||||
Glamourer.RevertableDesigns.RevertByNameWithoutApplication(characterName);
|
||||
}
|
||||
|
||||
private void Revert(Character? character)
|
||||
{
|
||||
if (character == null)
|
||||
return;
|
||||
Glamourer.RevertableDesigns.Revert(character);
|
||||
Glamourer.Penumbra.UpdateCharacters(character, null);
|
||||
}
|
||||
|
||||
private string? GetAllCustomization(Character? character)
|
||||
{
|
||||
if (character == null)
|
||||
return null;
|
||||
|
||||
CharacterSave save = new CharacterSave();
|
||||
save.LoadCharacter(character);
|
||||
return save.ToBase64();
|
||||
}
|
||||
|
||||
private string? GetAllCustomization(string characterName)
|
||||
{
|
||||
CharacterSave save = null!;
|
||||
foreach (var gameObject in _objectTable)
|
||||
{
|
||||
if (gameObject.Name.ToString() != characterName)
|
||||
continue;
|
||||
|
||||
var player = (Character)gameObject;
|
||||
save = new CharacterSave();
|
||||
save.LoadCharacter(player);
|
||||
break;
|
||||
}
|
||||
|
||||
return save?.ToBase64() ?? null;
|
||||
}
|
||||
//private static int GetApiVersion()
|
||||
// => CurrentApiVersion;
|
||||
//
|
||||
//private void ApplyAll(string customization, string characterName)
|
||||
//{
|
||||
// var save = CharacterSave.FromString(customization);
|
||||
// foreach (var gameObject in _objectTable)
|
||||
// {
|
||||
// if (gameObject.Name.ToString() != characterName)
|
||||
// continue;
|
||||
//
|
||||
// var player = (Character)gameObject;
|
||||
// Glamourer.RevertableDesigns.Revert(player);
|
||||
// save.Apply(player);
|
||||
// Glamourer.Penumbra.UpdateCharacters(player, null);
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private void ApplyAll(string customization, Character? character)
|
||||
//{
|
||||
// if (character == null)
|
||||
// return;
|
||||
// var save = CharacterSave.FromString(customization);
|
||||
// Glamourer.RevertableDesigns.Revert(character);
|
||||
// save.Apply(character);
|
||||
// Glamourer.Penumbra.UpdateCharacters(character, null);
|
||||
//}
|
||||
//
|
||||
//private void ApplyOnlyCustomization(string customization, string characterName)
|
||||
//{
|
||||
// var save = CharacterSave.FromString(customization);
|
||||
// foreach (var gameObject in _objectTable)
|
||||
// {
|
||||
// if (gameObject.Name.ToString() != characterName)
|
||||
// continue;
|
||||
//
|
||||
// var player = (Character)gameObject;
|
||||
// Glamourer.RevertableDesigns.Revert(player);
|
||||
// save.ApplyOnlyCustomizations(player);
|
||||
// Glamourer.Penumbra.UpdateCharacters(player, null);
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private void ApplyOnlyCustomization(string customization, Character? character)
|
||||
//{
|
||||
// if (character == null)
|
||||
// return;
|
||||
// var save = CharacterSave.FromString(customization);
|
||||
// Glamourer.RevertableDesigns.Revert(character);
|
||||
// save.ApplyOnlyCustomizations(character);
|
||||
// Glamourer.Penumbra.UpdateCharacters(character, null);
|
||||
//}
|
||||
//
|
||||
//private void ApplyOnlyEquipment(string customization, string characterName)
|
||||
//{
|
||||
// var save = CharacterSave.FromString(customization);
|
||||
// foreach (var gameObject in _objectTable)
|
||||
// {
|
||||
// if (gameObject.Name.ToString() != characterName)
|
||||
// continue;
|
||||
//
|
||||
// var player = (Character)gameObject;
|
||||
// Glamourer.RevertableDesigns.Revert(player);
|
||||
// save.ApplyOnlyEquipment(player);
|
||||
// Glamourer.Penumbra.UpdateCharacters(player, null);
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private void ApplyOnlyEquipment(string customization, Character? character)
|
||||
//{
|
||||
// if (character == null)
|
||||
// return;
|
||||
// var save = CharacterSave.FromString(customization);
|
||||
// Glamourer.RevertableDesigns.Revert(character);
|
||||
// save.ApplyOnlyEquipment(character);
|
||||
// Glamourer.Penumbra.UpdateCharacters(character, null);
|
||||
//}
|
||||
//
|
||||
//private void Revert(string characterName)
|
||||
//{
|
||||
// foreach (var gameObject in _objectTable)
|
||||
// {
|
||||
// if (gameObject.Name.ToString() != characterName)
|
||||
// continue;
|
||||
//
|
||||
// var player = (Character)gameObject;
|
||||
// Glamourer.RevertableDesigns.Revert(player);
|
||||
// Glamourer.Penumbra.UpdateCharacters(player, null);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// Glamourer.RevertableDesigns.RevertByNameWithoutApplication(characterName);
|
||||
//}
|
||||
//
|
||||
//private void Revert(Character? character)
|
||||
//{
|
||||
// if (character == null)
|
||||
// return;
|
||||
// Glamourer.RevertableDesigns.Revert(character);
|
||||
// Glamourer.Penumbra.UpdateCharacters(character, null);
|
||||
//}
|
||||
//
|
||||
//private string? GetAllCustomization(Character? character)
|
||||
//{
|
||||
// if (character == null)
|
||||
// return null;
|
||||
//
|
||||
// CharacterSave save = new CharacterSave();
|
||||
// save.LoadCharacter(character);
|
||||
// return save.ToBase64();
|
||||
//}
|
||||
//
|
||||
//private string? GetAllCustomization(string characterName)
|
||||
//{
|
||||
// CharacterSave save = null!;
|
||||
// foreach (var gameObject in _objectTable)
|
||||
// {
|
||||
// if (gameObject.Name.ToString() != characterName)
|
||||
// continue;
|
||||
//
|
||||
// var player = (Character)gameObject;
|
||||
// save = new CharacterSave();
|
||||
// save.LoadCharacter(player);
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// return save?.ToBase64() ?? null;
|
||||
//}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
|
|
@ -14,17 +13,17 @@ public class PenumbraAttach : IDisposable
|
|||
public const int RequiredPenumbraBreakingVersion = 4;
|
||||
public const int RequiredPenumbraFeatureVersion = 0;
|
||||
|
||||
private ICallGateSubscriber<ChangedItemType, uint, object>? _tooltipSubscriber;
|
||||
private ICallGateSubscriber<MouseButton, ChangedItemType, uint, object>? _clickSubscriber;
|
||||
private ICallGateSubscriber<string, int, object>? _redrawSubscriberName;
|
||||
private ICallGateSubscriber<GameObject, int, object>? _redrawSubscriberObject;
|
||||
private ICallGateSubscriber<IntPtr, (IntPtr, string)>? _drawObjectInfo;
|
||||
private ICallGateSubscriber<IntPtr, string, IntPtr, IntPtr, object?>? _creatingCharacterBase;
|
||||
private ICallGateSubscriber<ChangedItemType, uint, object>? _tooltipSubscriber;
|
||||
private ICallGateSubscriber<MouseButton, ChangedItemType, uint, object>? _clickSubscriber;
|
||||
private ICallGateSubscriber<string, int, object>? _redrawSubscriberName;
|
||||
private ICallGateSubscriber<GameObject, int, object>? _redrawSubscriberObject;
|
||||
private ICallGateSubscriber<IntPtr, (IntPtr, string)>? _drawObjectInfo;
|
||||
private ICallGateSubscriber<IntPtr, string, IntPtr, IntPtr, IntPtr, object?>? _creatingCharacterBase;
|
||||
|
||||
private readonly ICallGateSubscriber<object?> _initializedEvent;
|
||||
private readonly ICallGateSubscriber<object?> _disposedEvent;
|
||||
|
||||
public event Action<IntPtr, IntPtr, IntPtr>? CreatingCharacterBase;
|
||||
public event Action<IntPtr, IntPtr, IntPtr, IntPtr>? CreatingCharacterBase;
|
||||
|
||||
public PenumbraAttach(bool attach)
|
||||
{
|
||||
|
|
@ -61,7 +60,7 @@ public class PenumbraAttach : IDisposable
|
|||
_clickSubscriber =
|
||||
Dalamud.PluginInterface.GetIpcSubscriber<MouseButton, ChangedItemType, uint, object>("Penumbra.ChangedItemClick");
|
||||
_creatingCharacterBase =
|
||||
Dalamud.PluginInterface.GetIpcSubscriber<IntPtr, string, IntPtr, IntPtr, object?>("Penumbra.CreatingCharacterBase");
|
||||
Dalamud.PluginInterface.GetIpcSubscriber<IntPtr, string, IntPtr, IntPtr, IntPtr, object?>("Penumbra.CreatingCharacterBase");
|
||||
_tooltipSubscriber.Subscribe(PenumbraTooltip);
|
||||
_clickSubscriber.Subscribe(PenumbraRightClick);
|
||||
_creatingCharacterBase.Subscribe(SubscribeCharacterBase);
|
||||
|
|
@ -73,8 +72,8 @@ public class PenumbraAttach : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private void SubscribeCharacterBase(IntPtr gameObject, string _, IntPtr customize, IntPtr equipment)
|
||||
=> CreatingCharacterBase?.Invoke(gameObject, customize, equipment);
|
||||
private void SubscribeCharacterBase(IntPtr gameObject, string _, IntPtr modelId, IntPtr customize, IntPtr equipment)
|
||||
=> CreatingCharacterBase?.Invoke(gameObject, modelId, customize, equipment);
|
||||
|
||||
public void Unattach()
|
||||
{
|
||||
|
|
@ -111,27 +110,30 @@ public class PenumbraAttach : IDisposable
|
|||
if (button != MouseButton.Right || type != ChangedItemType.Item)
|
||||
return;
|
||||
|
||||
var gPose = Dalamud.Objects[Interface.GPoseObjectId] as Character;
|
||||
var player = Dalamud.Objects[0] as Character;
|
||||
var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(id)!;
|
||||
var writeItem = new Item(item, string.Empty);
|
||||
if (gPose != null)
|
||||
{
|
||||
writeItem.Write(gPose.Address);
|
||||
UpdateCharacters(gPose, player);
|
||||
}
|
||||
else if (player != null)
|
||||
{
|
||||
writeItem.Write(player.Address);
|
||||
UpdateCharacters(player);
|
||||
}
|
||||
//var gPose = ObjectManager.GPosePlayer;
|
||||
//var player = ObjectManager.Player;
|
||||
//var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(id)!;
|
||||
//var writeItem = new Item(item, string.Empty);
|
||||
//if (gPose != null)
|
||||
//{
|
||||
// writeItem.Write(gPose.Address);
|
||||
// UpdateCharacters(gPose, player);
|
||||
//}
|
||||
//else if (player != null)
|
||||
//{
|
||||
// writeItem.Write(player.Address);
|
||||
// UpdateCharacters(player);
|
||||
//}
|
||||
}
|
||||
|
||||
public unsafe FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* GameObjectFromDrawObject(IntPtr drawObject)
|
||||
=> (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)(_drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero);
|
||||
|
||||
public void RedrawObject(GameObject actor, RedrawType settings, bool repeat)
|
||||
public void RedrawObject(GameObject? actor, RedrawType settings, bool repeat)
|
||||
{
|
||||
if (actor == null)
|
||||
return;
|
||||
|
||||
if (_redrawSubscriberObject != null)
|
||||
{
|
||||
try
|
||||
|
|
@ -166,15 +168,13 @@ public class PenumbraAttach : IDisposable
|
|||
// then manually redraw using Penumbra.
|
||||
public void UpdateCharacters(Character character, Character? gPoseOriginalCharacter = null)
|
||||
{
|
||||
var newEquip = Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character);
|
||||
RedrawObject(character, RedrawType.Redraw, true);
|
||||
|
||||
// Special case for carrying over changes to the gPose player to the regular player, too.
|
||||
if (gPoseOriginalCharacter == null)
|
||||
return;
|
||||
|
||||
newEquip.Write(gPoseOriginalCharacter.Address);
|
||||
Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(gPoseOriginalCharacter);
|
||||
RedrawObject(gPoseOriginalCharacter, RedrawType.AfterGPose, false);
|
||||
//RedrawObject(character, RedrawType.Redraw, true);
|
||||
//
|
||||
//// Special case for carrying over changes to the gPose player to the regular player, too.
|
||||
//if (gPoseOriginalCharacter == null)
|
||||
// return;
|
||||
//
|
||||
//newEquip.Write(gPoseOriginalCharacter.Address);
|
||||
//RedrawObject(gPoseOriginalCharacter, RedrawType.AfterGPose, false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,265 +1,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Functions = Penumbra.GameData.Util.Functions;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public class CharacterSaveConverter : JsonConverter
|
||||
public class CharacterSaveConverter : JsonConverter<CharacterSave>
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
=> objectType == typeof(CharacterSave);
|
||||
public override void WriteJson(JsonWriter writer, CharacterSave value, JsonSerializer serializer)
|
||||
{
|
||||
var s = value.ToBase64();
|
||||
serializer.Serialize(writer, s);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
public override CharacterSave ReadJson(JsonReader reader, Type objectType, CharacterSave existingValue, bool hasExistingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
var token = JToken.Load(reader);
|
||||
var s = token.ToObject<string>();
|
||||
return CharacterSave.FromString(s!);
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
=> true;
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
var s = ((CharacterSave)value).ToBase64();
|
||||
serializer.Serialize(writer, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(CharacterSaveConverter))]
|
||||
public class CharacterSave
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public unsafe struct CharacterSave
|
||||
{
|
||||
public const byte CurrentVersion = 2;
|
||||
public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes;
|
||||
public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1;
|
||||
[Flags]
|
||||
public enum SaveFlags : byte
|
||||
{
|
||||
WriteCustomizations = 0x01,
|
||||
IsWet = 0x02,
|
||||
SetHatState = 0x04,
|
||||
SetWeaponState = 0x08,
|
||||
SetVisorState = 0x10,
|
||||
HatState = 0x20,
|
||||
WeaponState = 0x40,
|
||||
VisorState = 0x80,
|
||||
}
|
||||
|
||||
public const byte TotalSize = TotalSizeVersion2;
|
||||
public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes;
|
||||
public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes + 4 + 1;
|
||||
|
||||
private readonly byte[] _bytes = new byte[TotalSize];
|
||||
public const byte CurrentVersion = 3;
|
||||
public byte Version = CurrentVersion;
|
||||
public SaveFlags Flags = 0;
|
||||
public CharacterEquipMask Equip = 0;
|
||||
public CharacterWeapon MainHand = default;
|
||||
public CharacterWeapon OffHand = default;
|
||||
public ushort Padding = 0;
|
||||
public CharacterArmor Head = default;
|
||||
public CharacterArmor Body = default;
|
||||
public CharacterArmor Hands = default;
|
||||
public CharacterArmor Legs = default;
|
||||
public CharacterArmor Feet = default;
|
||||
public CharacterArmor Ears = default;
|
||||
public CharacterArmor Neck = default;
|
||||
public CharacterArmor Wrist = default;
|
||||
public CharacterArmor RFinger = default;
|
||||
public CharacterArmor LFinger = default;
|
||||
private CustomizationData CustomizationData = CustomizationData.Default;
|
||||
public float Alpha = 1f;
|
||||
|
||||
public CharacterSave()
|
||||
{
|
||||
_bytes[0] = CurrentVersion;
|
||||
Alpha = 1.0f;
|
||||
}
|
||||
{ }
|
||||
|
||||
public CharacterSave Copy()
|
||||
public void Load(Actor actor)
|
||||
{
|
||||
var ret = new CharacterSave();
|
||||
_bytes.CopyTo((Span<byte>)ret._bytes);
|
||||
return ret;
|
||||
}
|
||||
if (!actor.IsHuman || actor.Pointer->GameObject.DrawObject == null)
|
||||
return;
|
||||
|
||||
public byte Version
|
||||
=> _bytes[0];
|
||||
|
||||
public bool WriteCustomizations
|
||||
{
|
||||
get => (_bytes[1] & 0x01) != 0;
|
||||
set => _bytes[1] = (byte)(value ? _bytes[1] | 0x01 : _bytes[1] & ~0x01);
|
||||
}
|
||||
|
||||
public bool IsWet
|
||||
{
|
||||
get => (_bytes[1] & 0x02) != 0;
|
||||
set => _bytes[1] = (byte)(value ? _bytes[1] | 0x02 : _bytes[1] & ~0x02);
|
||||
}
|
||||
|
||||
public bool SetHatState
|
||||
{
|
||||
get => (_bytes[1] & 0x04) != 0;
|
||||
set => _bytes[1] = (byte)(value ? _bytes[1] | 0x04 : _bytes[1] & ~0x04);
|
||||
}
|
||||
|
||||
public bool SetWeaponState
|
||||
{
|
||||
get => (_bytes[1] & 0x08) != 0;
|
||||
set => _bytes[1] = (byte)(value ? _bytes[1] | 0x08 : _bytes[1] & ~0x08);
|
||||
}
|
||||
|
||||
public bool SetVisorState
|
||||
{
|
||||
get => (_bytes[1] & 0x10) != 0;
|
||||
set => _bytes[1] = (byte)(value ? _bytes[1] | 0x10 : _bytes[1] & ~0x10);
|
||||
}
|
||||
|
||||
public bool WriteProtected
|
||||
{
|
||||
get => (_bytes[1] & 0x20) != 0;
|
||||
set => _bytes[1] = (byte)(value ? _bytes[1] | 0x20 : _bytes[1] & ~0x20);
|
||||
}
|
||||
|
||||
public byte StateFlags
|
||||
{
|
||||
get => _bytes[64 + CharacterCustomization.CustomizationBytes];
|
||||
set => _bytes[64 + CharacterCustomization.CustomizationBytes] = value;
|
||||
}
|
||||
|
||||
public bool HatState
|
||||
{
|
||||
get => (StateFlags & 0x01) == 0;
|
||||
set => StateFlags = (byte)(value ? StateFlags & ~0x01 : StateFlags | 0x01);
|
||||
}
|
||||
|
||||
public bool VisorState
|
||||
{
|
||||
get => (StateFlags & 0x10) != 0;
|
||||
set => StateFlags = (byte)(value ? StateFlags | 0x10 : StateFlags & ~0x10);
|
||||
}
|
||||
|
||||
public bool WeaponState
|
||||
{
|
||||
get => (StateFlags & 0x02) == 0;
|
||||
set => StateFlags = (byte)(value ? StateFlags & ~0x02 : StateFlags | 0x02);
|
||||
}
|
||||
|
||||
public CharacterEquipMask WriteEquipment
|
||||
{
|
||||
get => (CharacterEquipMask)(_bytes[2] | (_bytes[3] << 8));
|
||||
set
|
||||
var human = (Human*)actor.Pointer->GameObject.DrawObject;
|
||||
CustomizationData = *(CustomizationData*)human->CustomizeData;
|
||||
fixed (void* equip = &Head)
|
||||
{
|
||||
_bytes[2] = (byte)((ushort)value & 0xFF);
|
||||
_bytes[3] = (byte)((ushort)value >> 8);
|
||||
Functions.MemCpyUnchecked(equip, human->EquipSlotData, sizeof(CharacterArmor) * 10);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<EquipSlot, (int, int, bool)> Offsets()
|
||||
{
|
||||
var stainOffsetWeapon = (int)Marshal.OffsetOf<CharacterWeapon>("Stain");
|
||||
var stainOffsetEquip = (int)Marshal.OffsetOf<CharacterArmor>("Stain");
|
||||
|
||||
(int, int, bool) ToOffsets(IntPtr offset, bool weapon)
|
||||
{
|
||||
var off = 4 + CharacterCustomization.CustomizationBytes + (int)offset;
|
||||
return weapon ? (off, off + stainOffsetWeapon, weapon) : (off, off + stainOffsetEquip, weapon);
|
||||
}
|
||||
|
||||
return new Dictionary<EquipSlot, (int, int, bool)>(12)
|
||||
{
|
||||
[EquipSlot.MainHand] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("MainHand"), true),
|
||||
[EquipSlot.OffHand] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("OffHand"), true),
|
||||
[EquipSlot.Head] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Head"), false),
|
||||
[EquipSlot.Body] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Body"), false),
|
||||
[EquipSlot.Hands] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Hands"), false),
|
||||
[EquipSlot.Legs] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Legs"), false),
|
||||
[EquipSlot.Feet] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Feet"), false),
|
||||
[EquipSlot.Ears] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Ears"), false),
|
||||
[EquipSlot.Neck] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Neck"), false),
|
||||
[EquipSlot.Wrists] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Wrists"), false),
|
||||
[EquipSlot.RFinger] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("RFinger"), false),
|
||||
[EquipSlot.LFinger] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("LFinger"), false),
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyDictionary<EquipSlot, (int, int, bool)> FieldOffsets = Offsets();
|
||||
|
||||
public bool WriteStain(EquipSlot slot, StainId stainId)
|
||||
{
|
||||
if (WriteProtected)
|
||||
return false;
|
||||
|
||||
var (_, stainOffset, _) = FieldOffsets[slot];
|
||||
if (_bytes[stainOffset] == (byte)stainId)
|
||||
return false;
|
||||
|
||||
_bytes[stainOffset] = stainId.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool WriteItem(int offset, SetId id, WeaponType type, ushort variant, bool weapon)
|
||||
{
|
||||
var idBytes = BitConverter.GetBytes(id.Value);
|
||||
|
||||
static bool WriteIfDifferent(ref byte x, byte y)
|
||||
{
|
||||
if (x == y)
|
||||
return false;
|
||||
|
||||
x = y;
|
||||
return true;
|
||||
}
|
||||
|
||||
var ret = WriteIfDifferent(ref _bytes[offset], idBytes[0]);
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 1], idBytes[1]);
|
||||
if (weapon)
|
||||
{
|
||||
var typeBytes = BitConverter.GetBytes(type.Value);
|
||||
var variantBytes = BitConverter.GetBytes(variant);
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 2], typeBytes[0]);
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 3], typeBytes[1]);
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 4], variantBytes[0]);
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 5], variantBytes[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 2], (byte)variant);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool WriteItem(Item item)
|
||||
{
|
||||
if (WriteProtected)
|
||||
return false;
|
||||
|
||||
var (itemOffset, _, isWeapon) = FieldOffsets[item.EquippableTo];
|
||||
var (id, type, variant) = item.MainModel;
|
||||
var ret = WriteItem(itemOffset, id, type, variant, isWeapon);
|
||||
if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel)
|
||||
{
|
||||
var (subOffset, _, _) = FieldOffsets[EquipSlot.OffHand];
|
||||
var (subId, subType, subVariant) = item.SubModel;
|
||||
ret |= WriteItem(subOffset, subId, subType, subVariant, true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public unsafe float Alpha
|
||||
public CharacterCustomization Customize
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* ptr = &_bytes[60 + CharacterCustomization.CustomizationBytes])
|
||||
fixed (CustomizationData* ptr = &CustomizationData)
|
||||
{
|
||||
return *(float*)ptr;
|
||||
return new CharacterCustomization(ptr);
|
||||
}
|
||||
}
|
||||
set
|
||||
}
|
||||
|
||||
public CharacterEquip Equipment
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* ptr = _bytes)
|
||||
fixed (CharacterArmor* ptr = &Head)
|
||||
{
|
||||
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 0) = *((byte*)&value + 0);
|
||||
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 1) = *((byte*)&value + 1);
|
||||
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 2) = *((byte*)&value + 2);
|
||||
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 3) = *((byte*)&value + 3);
|
||||
return new CharacterEquip(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(CharacterCustomization customization)
|
||||
{
|
||||
WriteCustomizations = true;
|
||||
customization.WriteBytes(_bytes, 4);
|
||||
}
|
||||
|
||||
public void Load(CharacterEquipment equipment, CharacterEquipMask mask = CharacterEquipMask.All)
|
||||
{
|
||||
WriteEquipment = mask;
|
||||
equipment.WriteBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes);
|
||||
}
|
||||
|
||||
public string ToBase64()
|
||||
=> Convert.ToBase64String(_bytes);
|
||||
{
|
||||
fixed (void* ptr = &this)
|
||||
{
|
||||
return Convert.ToBase64String(new ReadOnlySpan<byte>(ptr, sizeof(CharacterSave)));
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckSize(int length, int requiredLength)
|
||||
{
|
||||
|
|
@ -275,119 +128,41 @@ public class CharacterSave
|
|||
$"Can not parse Base64 string into CharacterSave:\n\tInvalid value {value} in byte {idx}, should be in [{min},{max}].");
|
||||
}
|
||||
|
||||
private static void CheckCharacterMask(byte val1, byte val2)
|
||||
public static CharacterSave FromString(string data)
|
||||
{
|
||||
var mask = (CharacterEquipMask)(val1 | (val2 << 8));
|
||||
if (mask > CharacterEquipMask.All)
|
||||
throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid value {mask} in byte 3 and 4.");
|
||||
}
|
||||
|
||||
public void LoadCharacter(Character a)
|
||||
{
|
||||
WriteCustomizations = true;
|
||||
Load(new CharacterCustomization(a));
|
||||
|
||||
Load(new CharacterEquipment(a));
|
||||
|
||||
SetHatState = true;
|
||||
SetVisorState = true;
|
||||
SetWeaponState = true;
|
||||
StateFlags = (byte)((a.IsHatVisible() ? 0x00 : 0x01) | (a.IsVisorToggled() ? 0x10 : 0x00) | (a.IsWeaponHidden() ? 0x02 : 0x00));
|
||||
|
||||
IsWet = a.IsWet();
|
||||
Alpha = a.Alpha();
|
||||
}
|
||||
|
||||
|
||||
public void Apply(Character a)
|
||||
{
|
||||
Glamourer.RevertableDesigns.Add(a);
|
||||
|
||||
if (WriteCustomizations)
|
||||
Customizations.Write(a.Address);
|
||||
if (WriteEquipment != CharacterEquipMask.None)
|
||||
Equipment.Write(a.Address, WriteEquipment, WriteEquipment);
|
||||
a.SetWetness(IsWet);
|
||||
a.Alpha() = Alpha;
|
||||
if (SetHatState)
|
||||
a.SetHatVisible(HatState);
|
||||
if (SetVisorState)
|
||||
a.SetVisorToggled(VisorState);
|
||||
if (SetWeaponState)
|
||||
a.SetWeaponHidden(!WeaponState);
|
||||
}
|
||||
|
||||
public void ApplyOnlyEquipment(Character a)
|
||||
{
|
||||
var oldState = _bytes[1];
|
||||
WriteCustomizations = false;
|
||||
SetHatState = false;
|
||||
SetVisorState = false;
|
||||
SetWeaponState = false;
|
||||
Apply(a);
|
||||
_bytes[1] = oldState;
|
||||
}
|
||||
|
||||
public void ApplyOnlyCustomizations(Character a)
|
||||
{
|
||||
var oldState = _bytes[1];
|
||||
SetHatState = false;
|
||||
SetVisorState = false;
|
||||
SetWeaponState = false;
|
||||
var oldEquip = WriteEquipment;
|
||||
WriteEquipment = CharacterEquipMask.None;
|
||||
Apply(a);
|
||||
_bytes[1] = oldState;
|
||||
WriteEquipment = oldEquip;
|
||||
}
|
||||
|
||||
public void Load(string base64)
|
||||
{
|
||||
var bytes = Convert.FromBase64String(base64);
|
||||
switch (bytes[0])
|
||||
var bytes = Convert.FromBase64String(data);
|
||||
var ret = new CharacterSave();
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
case 1:
|
||||
CheckSize(bytes.Length, TotalSizeVersion1);
|
||||
CheckRange(2, bytes[1], 0, 1);
|
||||
Alpha = 1.0f;
|
||||
bytes[0] = CurrentVersion;
|
||||
break;
|
||||
case 2:
|
||||
CheckSize(bytes.Length, TotalSizeVersion2);
|
||||
CheckRange(2, bytes[1], 0, 0x3F);
|
||||
break;
|
||||
default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}.");
|
||||
}
|
||||
|
||||
CheckCharacterMask(bytes[2], bytes[3]);
|
||||
bytes.CopyTo(_bytes, 0);
|
||||
}
|
||||
|
||||
public static CharacterSave FromString(string base64)
|
||||
{
|
||||
var ret = new CharacterSave();
|
||||
ret.Load(base64);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public unsafe ref CharacterCustomization Customizations
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* ptr = _bytes)
|
||||
switch (bytes[0])
|
||||
{
|
||||
return ref *(CharacterCustomization*)(ptr + 4);
|
||||
case 1:
|
||||
CheckSize(bytes.Length, TotalSizeVersion1);
|
||||
CheckRange(2, bytes[1], 0, 1);
|
||||
Functions.MemCpyUnchecked(&ret, ptr, TotalSizeVersion1);
|
||||
ret.Version = CurrentVersion;
|
||||
ret.Alpha = 1f;
|
||||
break;
|
||||
case 2:
|
||||
CheckSize(bytes.Length, TotalSizeVersion2);
|
||||
CheckRange(2, bytes[1], 0, 0x3F);
|
||||
Functions.MemCpyUnchecked(&ret, ptr, TotalSizeVersion2 - 1);
|
||||
ret.Flags &= ~SaveFlags.HatState;
|
||||
if ((bytes.Last() & 0x01) != 0)
|
||||
ret.Flags |= SaveFlags.HatState;
|
||||
if ((bytes.Last() & 0x02) != 0)
|
||||
ret.Flags |= SaveFlags.WeaponState;
|
||||
if ((bytes.Last() & 0x04) != 0)
|
||||
ret.Flags |= SaveFlags.VisorState;
|
||||
break;
|
||||
case 3:
|
||||
CheckSize(bytes.Length, sizeof(CharacterSave));
|
||||
Functions.MemCpyUnchecked(&ret, ptr, sizeof(CharacterSave));
|
||||
break;
|
||||
default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CharacterEquipment Equipment
|
||||
{
|
||||
get
|
||||
{
|
||||
var ret = new CharacterEquipment();
|
||||
ret.FromBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes);
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
124
Glamourer/CustomizeExtensions.cs
Normal file
124
Glamourer/CustomizeExtensions.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public static unsafe class CustomizeExtensions
|
||||
{
|
||||
// In languages other than english the actual clan name may depend on gender.
|
||||
public static string ClanName(SubRace race, Gender gender)
|
||||
{
|
||||
if (gender == Gender.FemaleNpc)
|
||||
gender = Gender.Female;
|
||||
if (gender == Gender.MaleNpc)
|
||||
gender = Gender.Male;
|
||||
return (gender, race) switch
|
||||
{
|
||||
(Gender.Male, SubRace.Midlander) => Glamourer.Customization.GetName(CustomName.MidlanderM),
|
||||
(Gender.Male, SubRace.Highlander) => Glamourer.Customization.GetName(CustomName.HighlanderM),
|
||||
(Gender.Male, SubRace.Wildwood) => Glamourer.Customization.GetName(CustomName.WildwoodM),
|
||||
(Gender.Male, SubRace.Duskwight) => Glamourer.Customization.GetName(CustomName.DuskwightM),
|
||||
(Gender.Male, SubRace.Plainsfolk) => Glamourer.Customization.GetName(CustomName.PlainsfolkM),
|
||||
(Gender.Male, SubRace.Dunesfolk) => Glamourer.Customization.GetName(CustomName.DunesfolkM),
|
||||
(Gender.Male, SubRace.SeekerOfTheSun) => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunM),
|
||||
(Gender.Male, SubRace.KeeperOfTheMoon) => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonM),
|
||||
(Gender.Male, SubRace.Seawolf) => Glamourer.Customization.GetName(CustomName.SeawolfM),
|
||||
(Gender.Male, SubRace.Hellsguard) => Glamourer.Customization.GetName(CustomName.HellsguardM),
|
||||
(Gender.Male, SubRace.Raen) => Glamourer.Customization.GetName(CustomName.RaenM),
|
||||
(Gender.Male, SubRace.Xaela) => Glamourer.Customization.GetName(CustomName.XaelaM),
|
||||
(Gender.Male, SubRace.Helion) => Glamourer.Customization.GetName(CustomName.HelionM),
|
||||
(Gender.Male, SubRace.Lost) => Glamourer.Customization.GetName(CustomName.LostM),
|
||||
(Gender.Male, SubRace.Rava) => Glamourer.Customization.GetName(CustomName.RavaM),
|
||||
(Gender.Male, SubRace.Veena) => Glamourer.Customization.GetName(CustomName.VeenaM),
|
||||
(Gender.Female, SubRace.Midlander) => Glamourer.Customization.GetName(CustomName.MidlanderF),
|
||||
(Gender.Female, SubRace.Highlander) => Glamourer.Customization.GetName(CustomName.HighlanderF),
|
||||
(Gender.Female, SubRace.Wildwood) => Glamourer.Customization.GetName(CustomName.WildwoodF),
|
||||
(Gender.Female, SubRace.Duskwight) => Glamourer.Customization.GetName(CustomName.DuskwightF),
|
||||
(Gender.Female, SubRace.Plainsfolk) => Glamourer.Customization.GetName(CustomName.PlainsfolkF),
|
||||
(Gender.Female, SubRace.Dunesfolk) => Glamourer.Customization.GetName(CustomName.DunesfolkF),
|
||||
(Gender.Female, SubRace.SeekerOfTheSun) => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunF),
|
||||
(Gender.Female, SubRace.KeeperOfTheMoon) => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonF),
|
||||
(Gender.Female, SubRace.Seawolf) => Glamourer.Customization.GetName(CustomName.SeawolfF),
|
||||
(Gender.Female, SubRace.Hellsguard) => Glamourer.Customization.GetName(CustomName.HellsguardF),
|
||||
(Gender.Female, SubRace.Raen) => Glamourer.Customization.GetName(CustomName.RaenF),
|
||||
(Gender.Female, SubRace.Xaela) => Glamourer.Customization.GetName(CustomName.XaelaF),
|
||||
(Gender.Female, SubRace.Helion) => Glamourer.Customization.GetName(CustomName.HelionM),
|
||||
(Gender.Female, SubRace.Lost) => Glamourer.Customization.GetName(CustomName.LostM),
|
||||
(Gender.Female, SubRace.Rava) => Glamourer.Customization.GetName(CustomName.RavaF),
|
||||
(Gender.Female, SubRace.Veena) => Glamourer.Customization.GetName(CustomName.VeenaF),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
|
||||
};
|
||||
}
|
||||
|
||||
public static string ClanName(this CharacterCustomization customize)
|
||||
=> ClanName(customize.Clan, customize.Gender);
|
||||
|
||||
// Change a gender and fix up all required customizations afterwards.
|
||||
public static bool ChangeGender(this CharacterCustomization customize, Gender gender, CharacterEquip equip)
|
||||
{
|
||||
if (customize.Gender == gender)
|
||||
return false;
|
||||
|
||||
customize.Gender = gender;
|
||||
FixUpAttributes(customize, equip);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change a race and fix up all required customizations afterwards.
|
||||
public static bool ChangeRace(this CharacterCustomization customize, SubRace clan, CharacterEquip equip)
|
||||
{
|
||||
if (customize.Clan == clan)
|
||||
return false;
|
||||
|
||||
var race = clan.ToRace();
|
||||
customize.Race = race;
|
||||
customize.Clan = clan;
|
||||
|
||||
// TODO: Female Hrothgar
|
||||
if (race == Race.Hrothgar)
|
||||
customize.Gender = Gender.Male;
|
||||
|
||||
FixUpAttributes(customize, equip);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Go through a whole customization struct and fix up all settings that need fixing.
|
||||
private static void FixUpAttributes(CharacterCustomization customize, CharacterEquip equip)
|
||||
{
|
||||
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
|
||||
foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId)))
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case CustomizationId.Race: break;
|
||||
case CustomizationId.Clan: break;
|
||||
case CustomizationId.BodyType: break;
|
||||
case CustomizationId.Gender: break;
|
||||
case CustomizationId.FacialFeaturesTattoos: break;
|
||||
case CustomizationId.HighlightsOnFlag: break;
|
||||
case CustomizationId.Face: break;
|
||||
default:
|
||||
var count = set.Count(id);
|
||||
if (set.DataByValue(id, customize[id], out _) < 0)
|
||||
customize[id] = count == 0 ? (byte)0 : set.Data(id, 0).Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!equip)
|
||||
return;
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var item = equip[slot];
|
||||
var (replaced, newSet, newVariant) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(item.Set, item.Variant, slot, customize.Race, customize.Gender);
|
||||
if (replaced)
|
||||
equip[slot] = new CharacterArmor(newSet, newVariant, item.Stain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,16 @@
|
|||
using Glamourer.FileSystem;
|
||||
using System;
|
||||
|
||||
namespace Glamourer.Designs
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class Design
|
||||
{
|
||||
public class Design : IFileSystemBase
|
||||
{
|
||||
public Folder Parent { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Name { get; }
|
||||
public bool ReadOnly;
|
||||
|
||||
public CharacterSave Data { get; set; }
|
||||
public DateTimeOffset CreationDate { get; }
|
||||
public DateTimeOffset LastUpdateDate { get; }
|
||||
public CharacterSave Data { get; }
|
||||
|
||||
internal Design(Folder parent, string name)
|
||||
{
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
Data = new CharacterSave();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,157 +3,155 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.FileSystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Glamourer.Designs
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class DesignManager
|
||||
{
|
||||
public class DesignManager
|
||||
{
|
||||
public const string FileName = "Designs.json";
|
||||
private readonly FileInfo _saveFile;
|
||||
|
||||
public SortedList<string, CharacterSave> Designs = null!;
|
||||
public FileSystem.FileSystem FileSystem { get; } = new();
|
||||
|
||||
public DesignManager()
|
||||
{
|
||||
var saveFolder = new DirectoryInfo(Dalamud.PluginInterface.GetPluginConfigDirectory());
|
||||
if (!saveFolder.Exists)
|
||||
Directory.CreateDirectory(saveFolder.FullName);
|
||||
|
||||
_saveFile = new FileInfo(Path.Combine(saveFolder.FullName, FileName));
|
||||
|
||||
LoadFromFile();
|
||||
}
|
||||
|
||||
private void BuildStructure()
|
||||
{
|
||||
FileSystem.Clear();
|
||||
var anyChanges = false;
|
||||
foreach (var (path, save) in Designs.ToArray())
|
||||
{
|
||||
try
|
||||
{
|
||||
var (folder, name) = FileSystem.CreateAllFolders(path);
|
||||
var design = new Design(folder, name) { Data = save };
|
||||
folder.FindOrAddChild(design);
|
||||
var fixedPath = design.FullName();
|
||||
if (string.Equals(fixedPath, path, StringComparison.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
Designs.Remove(path);
|
||||
Designs[fixedPath] = save;
|
||||
anyChanges = true;
|
||||
PluginLog.Debug($"Problem loading saved designs, {path} was renamed to {fixedPath}.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Problem loading saved designs, {path} was removed because:\n{e}");
|
||||
Designs.Remove(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (anyChanges)
|
||||
SaveToFile();
|
||||
}
|
||||
|
||||
private bool UpdateRoot(string oldPath, Design child)
|
||||
{
|
||||
var newPath = child.FullName();
|
||||
if (string.Equals(newPath, oldPath, StringComparison.InvariantCultureIgnoreCase))
|
||||
return false;
|
||||
|
||||
Designs.Remove(oldPath);
|
||||
Designs[child.FullName()] = child.Data;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateChild(string oldRootPath, string newRootPath, Design child)
|
||||
{
|
||||
var newPath = child.FullName();
|
||||
var oldPath = $"{oldRootPath}{newPath.Remove(0, newRootPath.Length)}";
|
||||
Designs.Remove(oldPath);
|
||||
Designs[newPath] = child.Data;
|
||||
}
|
||||
|
||||
public void DeleteAllChildren(IFileSystemBase root, bool deleteEmpty)
|
||||
{
|
||||
if (root is Folder f)
|
||||
foreach (var child in f.AllLeaves(SortMode.Lexicographical))
|
||||
Designs.Remove(child.FullName());
|
||||
var fullPath = root.FullName();
|
||||
root.Parent.RemoveChild(root, deleteEmpty);
|
||||
Designs.Remove(fullPath);
|
||||
|
||||
SaveToFile();
|
||||
}
|
||||
|
||||
public void UpdateAllChildren(string oldPath, IFileSystemBase root)
|
||||
{
|
||||
var changes = false;
|
||||
switch (root)
|
||||
{
|
||||
case Design d:
|
||||
changes |= UpdateRoot(oldPath, d);
|
||||
break;
|
||||
case Folder f:
|
||||
{
|
||||
var newRootPath = root.FullName();
|
||||
if (!string.Equals(oldPath, newRootPath, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
changes = true;
|
||||
foreach (var descendant in f.AllLeaves(SortMode.Lexicographical).Where(l => l is Design).Cast<Design>())
|
||||
UpdateChild(oldPath, newRootPath, descendant);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (changes)
|
||||
SaveToFile();
|
||||
}
|
||||
|
||||
public void SaveToFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = JsonConvert.SerializeObject(Designs, Formatting.Indented);
|
||||
File.WriteAllText(_saveFile.FullName, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Could not write to save file {_saveFile.FullName}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromFile()
|
||||
{
|
||||
_saveFile.Refresh();
|
||||
SortedList<string, CharacterSave>? designs = null;
|
||||
if (_saveFile.Exists)
|
||||
try
|
||||
{
|
||||
var data = File.ReadAllText(_saveFile.FullName);
|
||||
designs = JsonConvert.DeserializeObject<SortedList<string, CharacterSave>>(data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Could not load save file {_saveFile.FullName}:\n{e}");
|
||||
}
|
||||
|
||||
if (designs == null)
|
||||
{
|
||||
Designs = new SortedList<string, CharacterSave>();
|
||||
SaveToFile();
|
||||
}
|
||||
else
|
||||
{
|
||||
Designs = designs;
|
||||
}
|
||||
|
||||
BuildStructure();
|
||||
}
|
||||
}
|
||||
//public const string FileName = "Designs.json";
|
||||
//private readonly FileInfo _saveFile;
|
||||
//
|
||||
//public SortedList<string, CharacterSave> Designs = null!;
|
||||
//public FileSystem.FileSystem FileSystem { get; } = new();
|
||||
//
|
||||
//public DesignManager()
|
||||
//{
|
||||
// var saveFolder = new DirectoryInfo(Dalamud.PluginInterface.GetPluginConfigDirectory());
|
||||
// if (!saveFolder.Exists)
|
||||
// Directory.CreateDirectory(saveFolder.FullName);
|
||||
//
|
||||
// _saveFile = new FileInfo(Path.Combine(saveFolder.FullName, FileName));
|
||||
//
|
||||
// LoadFromFile();
|
||||
//}
|
||||
//
|
||||
//private void BuildStructure()
|
||||
//{
|
||||
// FileSystem.Clear();
|
||||
// var anyChanges = false;
|
||||
// foreach (var (path, save) in Designs.ToArray())
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// var (folder, name) = FileSystem.CreateAllFolders(path);
|
||||
// var design = new Design(folder, name) { Data = save };
|
||||
// folder.FindOrAddChild(design);
|
||||
// var fixedPath = design.FullName();
|
||||
// if (string.Equals(fixedPath, path, StringComparison.InvariantCultureIgnoreCase))
|
||||
// continue;
|
||||
//
|
||||
// Designs.Remove(path);
|
||||
// Designs[fixedPath] = save;
|
||||
// anyChanges = true;
|
||||
// PluginLog.Debug($"Problem loading saved designs, {path} was renamed to {fixedPath}.");
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Error($"Problem loading saved designs, {path} was removed because:\n{e}");
|
||||
// Designs.Remove(path);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (anyChanges)
|
||||
// SaveToFile();
|
||||
//}
|
||||
//
|
||||
//private bool UpdateRoot(string oldPath, Design child)
|
||||
//{
|
||||
// var newPath = child.FullName();
|
||||
// if (string.Equals(newPath, oldPath, StringComparison.InvariantCultureIgnoreCase))
|
||||
// return false;
|
||||
//
|
||||
// Designs.Remove(oldPath);
|
||||
// Designs[child.FullName()] = child.Data;
|
||||
// return true;
|
||||
//}
|
||||
//
|
||||
//private void UpdateChild(string oldRootPath, string newRootPath, Design child)
|
||||
//{
|
||||
// var newPath = child.FullName();
|
||||
// var oldPath = $"{oldRootPath}{newPath.Remove(0, newRootPath.Length)}";
|
||||
// Designs.Remove(oldPath);
|
||||
// Designs[newPath] = child.Data;
|
||||
//}
|
||||
//
|
||||
//public void DeleteAllChildren(IFileSystemBase root, bool deleteEmpty)
|
||||
//{
|
||||
// if (root is Folder f)
|
||||
// foreach (var child in f.AllLeaves(SortMode.Lexicographical))
|
||||
// Designs.Remove(child.FullName());
|
||||
// var fullPath = root.FullName();
|
||||
// root.Parent.RemoveChild(root, deleteEmpty);
|
||||
// Designs.Remove(fullPath);
|
||||
//
|
||||
// SaveToFile();
|
||||
//}
|
||||
//
|
||||
//public void UpdateAllChildren(string oldPath, IFileSystemBase root)
|
||||
//{
|
||||
// var changes = false;
|
||||
// switch (root)
|
||||
// {
|
||||
// case Design d:
|
||||
// changes |= UpdateRoot(oldPath, d);
|
||||
// break;
|
||||
// case Folder f:
|
||||
// {
|
||||
// var newRootPath = root.FullName();
|
||||
// if (!string.Equals(oldPath, newRootPath, StringComparison.InvariantCultureIgnoreCase))
|
||||
// {
|
||||
// changes = true;
|
||||
// foreach (var descendant in f.AllLeaves(SortMode.Lexicographical).Where(l => l is Design).Cast<Design>())
|
||||
// UpdateChild(oldPath, newRootPath, descendant);
|
||||
// }
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (changes)
|
||||
// SaveToFile();
|
||||
//}
|
||||
//
|
||||
//public void SaveToFile()
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// var data = JsonConvert.SerializeObject(Designs, Formatting.Indented);
|
||||
// File.WriteAllText(_saveFile.FullName, data);
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Error($"Could not write to save file {_saveFile.FullName}:\n{e}");
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public void LoadFromFile()
|
||||
//{
|
||||
// _saveFile.Refresh();
|
||||
// SortedList<string, CharacterSave>? designs = null;
|
||||
// if (_saveFile.Exists)
|
||||
// try
|
||||
// {
|
||||
// var data = File.ReadAllText(_saveFile.FullName);
|
||||
// designs = JsonConvert.DeserializeObject<SortedList<string, CharacterSave>>(data);
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Error($"Could not load save file {_saveFile.FullName}:\n{e}");
|
||||
// }
|
||||
//
|
||||
// if (designs == null)
|
||||
// {
|
||||
// Designs = new SortedList<string, CharacterSave>();
|
||||
// SaveToFile();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Designs = designs;
|
||||
// }
|
||||
//
|
||||
// BuildStructure();
|
||||
//}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,193 +1,188 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.FileSystem;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.PlayerWatch;
|
||||
|
||||
namespace Glamourer.Designs
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class FixedDesigns : IDisposable
|
||||
{
|
||||
public class FixedDesigns : IDisposable
|
||||
//public class FixedDesign
|
||||
//{
|
||||
// public string Name;
|
||||
// public JobGroup Jobs;
|
||||
// public Design Design;
|
||||
// public bool Enabled;
|
||||
//
|
||||
// public GlamourerConfig.FixedDesign ToSave()
|
||||
// => new()
|
||||
// {
|
||||
// Name = Name,
|
||||
// Path = Design.FullName(),
|
||||
// Enabled = Enabled,
|
||||
// JobGroups = Jobs.Id,
|
||||
// };
|
||||
//
|
||||
// public FixedDesign(string name, Design design, bool enabled, JobGroup jobs)
|
||||
// {
|
||||
// Name = name;
|
||||
// Design = design;
|
||||
// Enabled = enabled;
|
||||
// Jobs = jobs;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public List<FixedDesign> Data;
|
||||
//public Dictionary<string, List<FixedDesign>> EnabledDesigns;
|
||||
//public readonly IReadOnlyDictionary<ushort, JobGroup> JobGroups;
|
||||
//
|
||||
//public bool EnableDesign(FixedDesign design)
|
||||
//{
|
||||
// var changes = !design.Enabled;
|
||||
//
|
||||
// if (!EnabledDesigns.TryGetValue(design.Name, out var designs))
|
||||
// {
|
||||
// EnabledDesigns[design.Name] = new List<FixedDesign> { design };
|
||||
// // TODO
|
||||
// changes = true;
|
||||
// }
|
||||
// else if (!designs.Contains(design))
|
||||
// {
|
||||
// designs.Add(design);
|
||||
// changes = true;
|
||||
// }
|
||||
//
|
||||
// design.Enabled = true;
|
||||
// // TODO
|
||||
// //if (Glamourer.Config.ApplyFixedDesigns)
|
||||
// //{
|
||||
// // var character =
|
||||
// // CharacterFactory.Convert(Dalamud.Objects.FirstOrDefault(o
|
||||
// // => o.ObjectKind == ObjectKind.Player && o.Name.ToString() == design.Name));
|
||||
// // if (character != null)
|
||||
// // OnPlayerChange(character);
|
||||
// //}
|
||||
//
|
||||
// return changes;
|
||||
//}
|
||||
//
|
||||
//public bool DisableDesign(FixedDesign design)
|
||||
//{
|
||||
// if (!design.Enabled)
|
||||
// return false;
|
||||
//
|
||||
// design.Enabled = false;
|
||||
// if (!EnabledDesigns.TryGetValue(design.Name, out var designs))
|
||||
// return false;
|
||||
// if (!designs.Remove(design))
|
||||
// return false;
|
||||
//
|
||||
// if (designs.Count == 0)
|
||||
// {
|
||||
// EnabledDesigns.Remove(design.Name);
|
||||
// // TODO
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
//}
|
||||
//
|
||||
//public FixedDesigns(DesignManager designs)
|
||||
//{
|
||||
// JobGroups = GameData.JobGroups(Dalamud.GameData);
|
||||
// Data = new List<FixedDesign>(Glamourer.Config.FixedDesigns.Count);
|
||||
// EnabledDesigns = new Dictionary<string, List<FixedDesign>>(Glamourer.Config.FixedDesigns.Count);
|
||||
// var changes = false;
|
||||
// for (var i = 0; i < Glamourer.Config.FixedDesigns.Count; ++i)
|
||||
// {
|
||||
// var save = Glamourer.Config.FixedDesigns[i];
|
||||
// if (designs.FileSystem.Find(save.Path, out var d) && d is Design design)
|
||||
// {
|
||||
// if (!JobGroups.TryGetValue((ushort)save.JobGroups, out var jobGroup))
|
||||
// jobGroup = JobGroups[1];
|
||||
// Data.Add(new FixedDesign(save.Name, design, save.Enabled, jobGroup));
|
||||
// if (save.Enabled)
|
||||
// changes |= EnableDesign(Data.Last());
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// PluginLog.Warning($"{save.Path} does not exist anymore, removing {save.Name} from fixed designs.");
|
||||
// Glamourer.Config.FixedDesigns.RemoveAt(i--);
|
||||
// changes = true;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (changes)
|
||||
// Glamourer.Config.Save();
|
||||
//}
|
||||
//
|
||||
//private void OnPlayerChange(Character character)
|
||||
//{
|
||||
// //var name = character.Name.ToString();
|
||||
// //if (!EnabledDesigns.TryGetValue(name, out var designs))
|
||||
// // return;
|
||||
// //
|
||||
// //var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(character.ClassJob.Id));
|
||||
// //if (design == null)
|
||||
// // return;
|
||||
// //
|
||||
// //PluginLog.Debug("Redrawing {CharacterName} with {DesignName} for job {JobGroup}.", name, design.Design.FullName(),
|
||||
// // design.Jobs.Name);
|
||||
// //design.Design.Data.Apply(character);
|
||||
// //Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character);
|
||||
// //Glamourer.Penumbra.RedrawObject(character, RedrawType.Redraw, false);
|
||||
//}
|
||||
//
|
||||
//public void Add(string name, Design design, JobGroup group, bool enabled = false)
|
||||
//{
|
||||
// Data.Add(new FixedDesign(name, design, enabled, group));
|
||||
// Glamourer.Config.FixedDesigns.Add(Data.Last().ToSave());
|
||||
//
|
||||
// if (enabled)
|
||||
// EnableDesign(Data.Last());
|
||||
//
|
||||
// Glamourer.Config.Save();
|
||||
//}
|
||||
//
|
||||
//public void Remove(FixedDesign design)
|
||||
//{
|
||||
// var idx = Data.IndexOf(design);
|
||||
// if (idx < 0)
|
||||
// return;
|
||||
//
|
||||
// Data.RemoveAt(idx);
|
||||
// Glamourer.Config.FixedDesigns.RemoveAt(idx);
|
||||
// if (design.Enabled)
|
||||
// {
|
||||
// EnabledDesigns.Remove(design.Name);
|
||||
// // TODO
|
||||
// }
|
||||
//
|
||||
// Glamourer.Config.Save();
|
||||
//}
|
||||
//
|
||||
//public void Move(FixedDesign design, int newIdx)
|
||||
//{
|
||||
// if (newIdx < 0)
|
||||
// newIdx = 0;
|
||||
// if (newIdx >= Data.Count)
|
||||
// newIdx = Data.Count - 1;
|
||||
//
|
||||
// var idx = Data.IndexOf(design);
|
||||
// if (idx < 0 || idx == newIdx)
|
||||
// return;
|
||||
//
|
||||
// Data.RemoveAt(idx);
|
||||
// Data.Insert(newIdx, design);
|
||||
// Glamourer.Config.FixedDesigns.RemoveAt(idx);
|
||||
// Glamourer.Config.FixedDesigns.Insert(newIdx, design.ToSave());
|
||||
// Glamourer.Config.Save();
|
||||
//}
|
||||
//
|
||||
public void Dispose()
|
||||
{
|
||||
public class FixedDesign
|
||||
{
|
||||
public string Name;
|
||||
public JobGroup Jobs;
|
||||
public Design Design;
|
||||
public bool Enabled;
|
||||
|
||||
public GlamourerConfig.FixedDesign ToSave()
|
||||
=> new()
|
||||
{
|
||||
Name = Name,
|
||||
Path = Design.FullName(),
|
||||
Enabled = Enabled,
|
||||
JobGroups = Jobs.Id,
|
||||
};
|
||||
|
||||
public FixedDesign(string name, Design design, bool enabled, JobGroup jobs)
|
||||
{
|
||||
Name = name;
|
||||
Design = design;
|
||||
Enabled = enabled;
|
||||
Jobs = jobs;
|
||||
}
|
||||
}
|
||||
|
||||
public List<FixedDesign> Data;
|
||||
public Dictionary<string, List<FixedDesign>> EnabledDesigns;
|
||||
public readonly IReadOnlyDictionary<ushort, JobGroup> JobGroups;
|
||||
|
||||
public bool EnableDesign(FixedDesign design)
|
||||
{
|
||||
var changes = !design.Enabled;
|
||||
|
||||
if (!EnabledDesigns.TryGetValue(design.Name, out var designs))
|
||||
{
|
||||
EnabledDesigns[design.Name] = new List<FixedDesign> { design };
|
||||
Glamourer.PlayerWatcher.AddPlayerToWatch(design.Name);
|
||||
changes = true;
|
||||
}
|
||||
else if (!designs.Contains(design))
|
||||
{
|
||||
designs.Add(design);
|
||||
changes = true;
|
||||
}
|
||||
|
||||
design.Enabled = true;
|
||||
if (Glamourer.Config.ApplyFixedDesigns)
|
||||
{
|
||||
var character =
|
||||
CharacterFactory.Convert(Dalamud.Objects.FirstOrDefault(o
|
||||
=> o.ObjectKind == ObjectKind.Player && o.Name.ToString() == design.Name));
|
||||
if (character != null)
|
||||
OnPlayerChange(character);
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
public bool DisableDesign(FixedDesign design)
|
||||
{
|
||||
if (!design.Enabled)
|
||||
return false;
|
||||
|
||||
design.Enabled = false;
|
||||
if (!EnabledDesigns.TryGetValue(design.Name, out var designs))
|
||||
return false;
|
||||
if (!designs.Remove(design))
|
||||
return false;
|
||||
|
||||
if (designs.Count == 0)
|
||||
{
|
||||
EnabledDesigns.Remove(design.Name);
|
||||
Glamourer.PlayerWatcher.RemovePlayerFromWatch(design.Name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public FixedDesigns(DesignManager designs)
|
||||
{
|
||||
JobGroups = GameData.JobGroups(Dalamud.GameData);
|
||||
Data = new List<FixedDesign>(Glamourer.Config.FixedDesigns.Count);
|
||||
EnabledDesigns = new Dictionary<string, List<FixedDesign>>(Glamourer.Config.FixedDesigns.Count);
|
||||
Glamourer.PlayerWatcher.PlayerChanged += OnPlayerChange;
|
||||
var changes = false;
|
||||
for (var i = 0; i < Glamourer.Config.FixedDesigns.Count; ++i)
|
||||
{
|
||||
var save = Glamourer.Config.FixedDesigns[i];
|
||||
if (designs.FileSystem.Find(save.Path, out var d) && d is Design design)
|
||||
{
|
||||
if (!JobGroups.TryGetValue((ushort) save.JobGroups, out var jobGroup))
|
||||
jobGroup = JobGroups[1];
|
||||
Data.Add(new FixedDesign(save.Name, design, save.Enabled, jobGroup));
|
||||
if (save.Enabled)
|
||||
changes |= EnableDesign(Data.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Warning($"{save.Path} does not exist anymore, removing {save.Name} from fixed designs.");
|
||||
Glamourer.Config.FixedDesigns.RemoveAt(i--);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changes)
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
private void OnPlayerChange(Character character)
|
||||
{
|
||||
//var name = character.Name.ToString();
|
||||
//if (!EnabledDesigns.TryGetValue(name, out var designs))
|
||||
// return;
|
||||
//
|
||||
//var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(character.ClassJob.Id));
|
||||
//if (design == null)
|
||||
// return;
|
||||
//
|
||||
//PluginLog.Debug("Redrawing {CharacterName} with {DesignName} for job {JobGroup}.", name, design.Design.FullName(),
|
||||
// design.Jobs.Name);
|
||||
//design.Design.Data.Apply(character);
|
||||
//Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character);
|
||||
//Glamourer.Penumbra.RedrawObject(character, RedrawType.Redraw, false);
|
||||
}
|
||||
|
||||
public void Add(string name, Design design, JobGroup group, bool enabled = false)
|
||||
{
|
||||
Data.Add(new FixedDesign(name, design, enabled, group));
|
||||
Glamourer.Config.FixedDesigns.Add(Data.Last().ToSave());
|
||||
|
||||
if (enabled)
|
||||
EnableDesign(Data.Last());
|
||||
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
public void Remove(FixedDesign design)
|
||||
{
|
||||
var idx = Data.IndexOf(design);
|
||||
if (idx < 0)
|
||||
return;
|
||||
|
||||
Data.RemoveAt(idx);
|
||||
Glamourer.Config.FixedDesigns.RemoveAt(idx);
|
||||
if (design.Enabled)
|
||||
{
|
||||
EnabledDesigns.Remove(design.Name);
|
||||
Glamourer.PlayerWatcher.RemovePlayerFromWatch(design.Name);
|
||||
}
|
||||
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
public void Move(FixedDesign design, int newIdx)
|
||||
{
|
||||
if (newIdx < 0)
|
||||
newIdx = 0;
|
||||
if (newIdx >= Data.Count)
|
||||
newIdx = Data.Count - 1;
|
||||
|
||||
var idx = Data.IndexOf(design);
|
||||
if (idx < 0 || idx == newIdx)
|
||||
return;
|
||||
|
||||
Data.RemoveAt(idx);
|
||||
Data.Insert(newIdx, design);
|
||||
Glamourer.Config.FixedDesigns.RemoveAt(idx);
|
||||
Glamourer.Config.FixedDesigns.Insert(newIdx, design.ToSave());
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Glamourer.Config.FixedDesigns = Data.Select(d => d.ToSave()).ToList();
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
//Glamourer.Config.FixedDesigns = Data.Select(d => d.ToSave()).ToList();
|
||||
//Glamourer.Config.Save();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,40 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
namespace Glamourer.Designs
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class RevertableDesigns
|
||||
{
|
||||
public class RevertableDesigns
|
||||
public readonly Dictionary<string, CharacterSave> Saves = new();
|
||||
|
||||
public bool Add(Character actor)
|
||||
{
|
||||
public readonly ConcurrentDictionary<string, CharacterSave> Saves = new();
|
||||
//var name = actor.Name.ToString();
|
||||
//if (Saves.TryGetValue(name, out var save))
|
||||
// return false;
|
||||
//
|
||||
//save = new CharacterSave();
|
||||
//save.LoadCharacter(actor);
|
||||
//Saves[name] = save;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Add(Character actor)
|
||||
{
|
||||
var name = actor.Name.ToString();
|
||||
if (Saves.TryGetValue(name, out var save))
|
||||
return false;
|
||||
public bool RevertByNameWithoutApplication(string actorName)
|
||||
{
|
||||
if (!Saves.ContainsKey(actorName))
|
||||
return false;
|
||||
|
||||
save = new CharacterSave();
|
||||
save.LoadCharacter(actor);
|
||||
Saves[name] = save;
|
||||
return true;
|
||||
}
|
||||
Saves.Remove(actorName);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RevertByNameWithoutApplication(string actorName)
|
||||
{
|
||||
if (!Saves.ContainsKey(actorName))
|
||||
return false;
|
||||
|
||||
Saves.Remove(actorName, out _);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Revert(Character actor)
|
||||
{
|
||||
if (!Saves.TryGetValue(actor.Name.ToString(), out var save))
|
||||
return false;
|
||||
|
||||
save.Apply(actor);
|
||||
Saves.Remove(actor.Name.ToString(), out _);
|
||||
return true;
|
||||
}
|
||||
public bool Revert(Character actor)
|
||||
{
|
||||
//if (!Saves.TryGetValue(actor.Name.ToString(), out var save))
|
||||
// return false;
|
||||
//
|
||||
//save.Apply(actor);
|
||||
//Saves.Remove(actor.Name.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,153 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Glamourer.FileSystem
|
||||
{
|
||||
public class FileSystem
|
||||
{
|
||||
public Folder Root { get; } = Folder.CreateRoot();
|
||||
|
||||
public void Clear()
|
||||
=> Root.Children.Clear();
|
||||
|
||||
// Find a specific child by its path from Root.
|
||||
// Returns true if the folder was found, and false if not.
|
||||
// The out parameter will contain the furthest existing folder.
|
||||
public bool Find(string path, out IFileSystemBase child)
|
||||
{
|
||||
var split = Split(path);
|
||||
var folder = Root;
|
||||
child = Root;
|
||||
foreach (var part in split)
|
||||
{
|
||||
if (!folder.FindChild(part, out var c))
|
||||
{
|
||||
child = folder;
|
||||
return false;
|
||||
}
|
||||
|
||||
child = c;
|
||||
if (c is not Folder f)
|
||||
return part == split.Last();
|
||||
|
||||
folder = f;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Folder CreateAllFolders(IEnumerable<string> names)
|
||||
{
|
||||
var last = Root;
|
||||
foreach (var name in names)
|
||||
last = last.FindOrCreateSubFolder(name).Item1;
|
||||
return last;
|
||||
}
|
||||
|
||||
public (Folder, string) CreateAllFolders(string path)
|
||||
{
|
||||
if (!path.Any())
|
||||
return (Root, string.Empty);
|
||||
|
||||
var split = Split(path);
|
||||
if (split.Length == 1)
|
||||
return (Root, path);
|
||||
|
||||
return (CreateAllFolders(split.Take(split.Length - 1)), split.Last());
|
||||
}
|
||||
|
||||
public bool Rename(IFileSystemBase child, string newName)
|
||||
{
|
||||
if (ReferenceEquals(child, Root))
|
||||
throw new InvalidOperationException("Can not rename root.");
|
||||
|
||||
newName = FixName(newName);
|
||||
if (child.Name == newName)
|
||||
return false;
|
||||
|
||||
if (child.Parent.FindChild(newName, out var preExisting))
|
||||
{
|
||||
if (MergeIfFolders(child, preExisting, false))
|
||||
return true;
|
||||
|
||||
throw new Exception($"Can not rename {child.Name} in {child.Parent.FullName()} to {newName} because {newName} already exists.");
|
||||
}
|
||||
|
||||
var parent = child.Parent;
|
||||
parent.RemoveChildIgnoreEmpty(child);
|
||||
child.Name = newName;
|
||||
parent.FindOrAddChild(child);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Move(IFileSystemBase child, Folder newParent, bool deleteEmpty)
|
||||
{
|
||||
var oldParent = child.Parent;
|
||||
if (ReferenceEquals(newParent, oldParent))
|
||||
return false;
|
||||
|
||||
// Moving into its own subfolder or itself is not allowed.
|
||||
if (child.IsFolder(out var f)
|
||||
&& (ReferenceEquals(newParent, f)
|
||||
|| newParent.FullName().StartsWith(f.FullName(), StringComparison.InvariantCultureIgnoreCase)))
|
||||
return false;
|
||||
|
||||
if (newParent.FindChild(child.Name, out var conflict))
|
||||
{
|
||||
if (MergeIfFolders(child, conflict, deleteEmpty))
|
||||
return true;
|
||||
|
||||
throw new Exception($"Can not move {child.Name} into {newParent.FullName()} because {conflict.FullName()} already exists.");
|
||||
}
|
||||
|
||||
oldParent.RemoveChild(child, deleteEmpty);
|
||||
newParent.FindOrAddChild(child);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Merge(Folder source, Folder target, bool deleteEmpty)
|
||||
{
|
||||
if (ReferenceEquals(source, target))
|
||||
return false;
|
||||
|
||||
if (!source.Children.Any())
|
||||
{
|
||||
if (deleteEmpty)
|
||||
{
|
||||
source.Parent.RemoveChild(source, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
while (source.Children.Count > 0)
|
||||
Move(source.Children.First(), target, deleteEmpty); // Can throw.
|
||||
|
||||
source.Parent.RemoveChild(source, deleteEmpty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool MergeIfFolders(IFileSystemBase source, IFileSystemBase target, bool deleteEmpty)
|
||||
{
|
||||
if (source is Folder childF && target.IsFolder(out var preF))
|
||||
{
|
||||
Merge(childF, preF, deleteEmpty);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string[] Split(string path)
|
||||
=> path.Split(new[]
|
||||
{
|
||||
'/',
|
||||
}, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
private static string FixName(string name)
|
||||
=> name.Replace('/', '\\');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
using System;
|
||||
using Dalamud.Logging;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Glamourer.FileSystem
|
||||
{
|
||||
public static class FileSystemImGui
|
||||
{
|
||||
public const string DraggedObjectLabel = "FSDrag";
|
||||
|
||||
private static unsafe bool IsDropping(string name)
|
||||
=> ImGui.AcceptDragDropPayload(name).NativePtr != null;
|
||||
|
||||
private static IFileSystemBase? _draggedObject;
|
||||
|
||||
public static bool DragDropTarget(FileSystem fs, IFileSystemBase child, out string oldPath, out IFileSystemBase? draggedChild)
|
||||
{
|
||||
oldPath = string.Empty;
|
||||
draggedChild = null;
|
||||
var ret = false;
|
||||
if (!ImGui.BeginDragDropTarget())
|
||||
return ret;
|
||||
|
||||
if (IsDropping(DraggedObjectLabel))
|
||||
{
|
||||
if (_draggedObject != null)
|
||||
try
|
||||
{
|
||||
oldPath = _draggedObject.FullName();
|
||||
draggedChild = _draggedObject;
|
||||
ret = fs.Move(_draggedObject, child.IsFolder(out var folder) ? folder : child.Parent, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Could not drag {_draggedObject.Name} onto {child.FullName()}:\n{e}");
|
||||
}
|
||||
|
||||
_draggedObject = null;
|
||||
}
|
||||
|
||||
ImGui.EndDragDropTarget();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void DragDropSource(IFileSystemBase child)
|
||||
{
|
||||
if (!ImGui.BeginDragDropSource())
|
||||
return;
|
||||
|
||||
ImGui.SetDragDropPayload(DraggedObjectLabel, IntPtr.Zero, 0);
|
||||
ImGui.Text($"Moving {child.Name}...");
|
||||
_draggedObject = child;
|
||||
ImGui.EndDragDropSource();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Glamourer.FileSystem
|
||||
{
|
||||
public enum SortMode
|
||||
{
|
||||
FoldersFirst = 0x00,
|
||||
Lexicographical = 0x01,
|
||||
}
|
||||
|
||||
public class Folder : IFileSystemBase
|
||||
{
|
||||
public Folder Parent { get; set; }
|
||||
public string Name { get; set; }
|
||||
public readonly List<IFileSystemBase> Children = new();
|
||||
|
||||
public Folder(Folder parent, string name)
|
||||
{
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> this.FullName();
|
||||
|
||||
// Return the number of all leaves with this folder in their path.
|
||||
public int TotalDescendantLeaves()
|
||||
{
|
||||
var sum = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
switch (child)
|
||||
{
|
||||
case Folder f:
|
||||
sum += f.TotalDescendantLeaves();
|
||||
break;
|
||||
case Link l:
|
||||
sum += l.Data is Folder fl ? fl.TotalDescendantLeaves() : 1;
|
||||
break;
|
||||
default:
|
||||
++sum;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Return all descendant mods in the specified order.
|
||||
public IEnumerable<IFileSystemBase> AllLeaves(SortMode mode)
|
||||
{
|
||||
return GetSortedEnumerator(mode).SelectMany(f =>
|
||||
{
|
||||
if (f.IsFolder(out var folder))
|
||||
return folder.AllLeaves(mode);
|
||||
|
||||
return new[]
|
||||
{
|
||||
f,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<IFileSystemBase> AllChildren(SortMode mode)
|
||||
=> GetSortedEnumerator(mode);
|
||||
|
||||
// Get an enumerator for actually sorted objects instead of folder-first objects.
|
||||
private IEnumerable<IFileSystemBase> GetSortedEnumerator(SortMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case SortMode.FoldersFirst:
|
||||
foreach (var child in Children.Where(c => c.IsFolder()))
|
||||
yield return child;
|
||||
|
||||
foreach (var child in Children.Where(c => c.IsLeaf()))
|
||||
yield return child;
|
||||
|
||||
break;
|
||||
case SortMode.Lexicographical:
|
||||
foreach (var child in Children)
|
||||
yield return child;
|
||||
|
||||
break;
|
||||
default: throw new InvalidEnumArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
internal static Folder CreateRoot()
|
||||
=> new(null!, "");
|
||||
|
||||
// Find a subfolder by name. Returns true and sets folder to it if it exists.
|
||||
public bool FindChild(string name, out IFileSystemBase ret)
|
||||
{
|
||||
var idx = Search(name);
|
||||
ret = idx >= 0 ? Children[idx] : this;
|
||||
return idx >= 0;
|
||||
}
|
||||
|
||||
// Checks if an equivalent child to child already exists and returns its index.
|
||||
// If it does not exist, inserts child as a child and returns the new index.
|
||||
// Also sets this as childs parent.
|
||||
public int FindOrAddChild(IFileSystemBase child)
|
||||
{
|
||||
var idx = Search(child);
|
||||
if (idx >= 0)
|
||||
return idx;
|
||||
|
||||
idx = ~idx;
|
||||
Children.Insert(idx, child);
|
||||
child.Parent = this;
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Checks if an equivalent child to child already exists and throws if it does.
|
||||
// If it does not exist, inserts child as a child and returns the new index.
|
||||
// Also sets this as childs parent.
|
||||
public int AddChild(IFileSystemBase child)
|
||||
{
|
||||
var idx = Search(child);
|
||||
if (idx >= 0)
|
||||
throw new Exception("Could not add child: Child of that name already exists.");
|
||||
|
||||
idx = ~idx;
|
||||
Children.Insert(idx, child);
|
||||
child.Parent = this;
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Checks if a subfolder with the given name already exists and returns it and its index.
|
||||
// If it does not exists, creates and inserts it and returns the new subfolder and its index.
|
||||
public (Folder, int) FindOrCreateSubFolder(string name)
|
||||
{
|
||||
var subFolder = new Folder(this, name);
|
||||
var idx = FindOrAddChild(subFolder);
|
||||
var child = Children[idx];
|
||||
if (!child.IsFolder(out var folder))
|
||||
throw new Exception($"The child {name} already exists in {this.FullName()} but is not a folder.");
|
||||
|
||||
return (folder, idx);
|
||||
}
|
||||
|
||||
// Remove child if it exists.
|
||||
// If this folder is empty afterwards and deleteEmpty is true, remove it from its parent.
|
||||
public void RemoveChild(IFileSystemBase child, bool deleteEmpty)
|
||||
{
|
||||
RemoveChildIgnoreEmpty(child);
|
||||
if (deleteEmpty)
|
||||
CheckEmpty();
|
||||
}
|
||||
|
||||
private void CheckEmpty()
|
||||
{
|
||||
if (Children.Count == 0)
|
||||
Parent?.RemoveChild(this, true);
|
||||
}
|
||||
|
||||
// Remove a child but do not remove this folder from its parent if it is empty afterwards.
|
||||
internal void RemoveChildIgnoreEmpty(IFileSystemBase folder)
|
||||
{
|
||||
var idx = Search(folder);
|
||||
if (idx < 0)
|
||||
return;
|
||||
|
||||
Children[idx].Parent = null!;
|
||||
Children.RemoveAt(idx);
|
||||
}
|
||||
|
||||
private int Search(string name)
|
||||
=> Children.BinarySearch(new FileSystemObject(name), FolderStructureComparer.Default);
|
||||
|
||||
private int Search(IFileSystemBase child)
|
||||
=> Children.BinarySearch(child, FolderStructureComparer.Default);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Glamourer.FileSystem
|
||||
{
|
||||
internal class FolderStructureComparer : IComparer<IFileSystemBase>
|
||||
{
|
||||
// Compare only the direct folder names since this is only used inside an enumeration of children of one folder.
|
||||
public static int Cmp(IFileSystemBase? x, IFileSystemBase? y)
|
||||
=> ReferenceEquals(x, y) ? 0 : string.Compare(x?.Name, y?.Name, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
public int Compare(IFileSystemBase? x, IFileSystemBase? y)
|
||||
=> Cmp(x, y);
|
||||
|
||||
internal static readonly FolderStructureComparer Default = new();
|
||||
}
|
||||
|
||||
public interface IFileSystemBase
|
||||
{
|
||||
public Folder Parent { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public static class FileSystemExtensions
|
||||
{
|
||||
public static string FullName(this IFileSystemBase data)
|
||||
=> data.Parent?.Name.Any() ?? false ? $"{data.Parent.FullName()}/{data.Name}" : data.Name;
|
||||
|
||||
public static bool IsLeaf(this IFileSystemBase data)
|
||||
=> data is not Folder && data is not Link { Data: Folder };
|
||||
|
||||
public static bool IsFolder(this IFileSystemBase data)
|
||||
=> data.IsFolder(out _);
|
||||
|
||||
public static bool IsFolder(this IFileSystemBase data, out Folder folder)
|
||||
{
|
||||
switch (data)
|
||||
{
|
||||
case Folder f:
|
||||
folder = f;
|
||||
return true;
|
||||
case Link { Data: Folder fl }:
|
||||
folder = fl;
|
||||
return true;
|
||||
default:
|
||||
folder = null!;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class FileSystemObject : IFileSystemBase
|
||||
{
|
||||
public FileSystemObject(string name)
|
||||
=> Name = name;
|
||||
|
||||
public Folder Parent { get; set; } = null!;
|
||||
public string Name { get; set; }
|
||||
|
||||
public string FullName()
|
||||
=> Name;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
using Glamourer.Designs;
|
||||
|
||||
namespace Glamourer.FileSystem
|
||||
{
|
||||
public class Link : IFileSystemBase
|
||||
{
|
||||
public Folder Parent { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public IFileSystemBase Data { get; }
|
||||
|
||||
public Link(Folder parent, string name, IFileSystemBase data)
|
||||
{
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,262 +1,173 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.Attributes;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.Excel;
|
||||
using Glamourer.Api;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.FileSystem;
|
||||
using Glamourer.Gui;
|
||||
using ImGuiNET;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Lumina.Data.Parsing;
|
||||
using Microsoft.VisualBasic.CompilerServices;
|
||||
using OtterGui.Table;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.PlayerWatch;
|
||||
using Penumbra.GameData.Structs;
|
||||
using static FFXIVClientStructs.FFXIV.Client.UI.Misc.RaptureMacroModule;
|
||||
using static System.Collections.Specialized.BitVector32;
|
||||
using static System.Reflection.Metadata.BlobBuilder;
|
||||
using Race = Lumina.Excel.GeneratedSheets.Race;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public unsafe class FixedDesignManager : IDisposable
|
||||
{
|
||||
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, uint* data);
|
||||
|
||||
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A",
|
||||
DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
public Hook<FlagSlotForUpdateDelegate>? FlagSlotForUpdateHook;
|
||||
|
||||
public readonly FixedDesigns FixedDesigns;
|
||||
|
||||
public FixedDesignManager(DesignManager designs)
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
FixedDesigns = new FixedDesigns(designs);
|
||||
|
||||
|
||||
if (Glamourer.Config.ApplyFixedDesigns)
|
||||
Enable();
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
FlagSlotForUpdateHook?.Enable();
|
||||
Glamourer.Penumbra.CreatingCharacterBase += ApplyFixedDesign;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
FlagSlotForUpdateHook?.Disable();
|
||||
Glamourer.Penumbra.CreatingCharacterBase -= ApplyFixedDesign;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
FlagSlotForUpdateHook?.Dispose();
|
||||
}
|
||||
|
||||
private void ApplyFixedDesign(IntPtr addr, IntPtr customize, IntPtr equipData)
|
||||
{
|
||||
var human = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)addr;
|
||||
if (human->GameObject.ObjectKind is (byte)ObjectKind.EventNpc or (byte)ObjectKind.BattleNpc or (byte)ObjectKind.Player
|
||||
&& human->ModelCharaId == 0)
|
||||
{
|
||||
var name = new Utf8String(human->GameObject.Name).ToString();
|
||||
if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs))
|
||||
{
|
||||
var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(human->ClassJob));
|
||||
if (design != null)
|
||||
{
|
||||
if (design.Design.Data.WriteCustomizations)
|
||||
*(CharacterCustomization*)customize = design.Design.Data.Customizations;
|
||||
|
||||
var data = (uint*)equipData;
|
||||
for (var i = 0u; i < 10; ++i)
|
||||
{
|
||||
var slot = i.ToEquipSlot();
|
||||
if (design.Design.Data.WriteEquipment.Fits(slot))
|
||||
data[i] = slot switch
|
||||
{
|
||||
EquipSlot.Head => design.Design.Data.Equipment.Head.Value,
|
||||
EquipSlot.Body => design.Design.Data.Equipment.Body.Value,
|
||||
EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value,
|
||||
EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value,
|
||||
EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value,
|
||||
EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value,
|
||||
EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value,
|
||||
EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value,
|
||||
EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value,
|
||||
EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, uint* data)
|
||||
{
|
||||
ulong ret;
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
try
|
||||
{
|
||||
if (slot != EquipSlot.Unknown)
|
||||
{
|
||||
var gameObject =
|
||||
(FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||
if (gameObject != null)
|
||||
{
|
||||
var name = new Utf8String(gameObject->GameObject.Name).ToString();
|
||||
if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs))
|
||||
{
|
||||
var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(gameObject->ClassJob));
|
||||
if (design != null && design.Design.Data.WriteEquipment.Fits(slot))
|
||||
*data = slot switch
|
||||
{
|
||||
EquipSlot.Head => design.Design.Data.Equipment.Head.Value,
|
||||
EquipSlot.Body => design.Design.Data.Equipment.Body.Value,
|
||||
EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value,
|
||||
EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value,
|
||||
EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value,
|
||||
EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value,
|
||||
EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value,
|
||||
EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value,
|
||||
EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value,
|
||||
EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ret = FlagSlotForUpdateHook!.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public class Glamourer : IDalamudPlugin
|
||||
{
|
||||
private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],<Name for Save>";
|
||||
private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],<Name for Save>";
|
||||
private const string MainCommandString = "/glamourer";
|
||||
private const string ApplyCommandString = "/glamour";
|
||||
|
||||
public string Name
|
||||
=> "Glamourer";
|
||||
|
||||
public static GlamourerConfig Config = null!;
|
||||
public static IPlayerWatcher PlayerWatcher = null!;
|
||||
public static ICustomizationManager Customization = null!;
|
||||
public static FixedDesignManager FixedDesignManager = null!;
|
||||
private readonly Interface _interface;
|
||||
public readonly DesignManager Designs;
|
||||
public static readonly string Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty;
|
||||
|
||||
public static RevertableDesigns RevertableDesigns = new();
|
||||
public readonly GlamourerIpc GlamourerIpc;
|
||||
public static readonly string CommitHash =
|
||||
Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "Unknown";
|
||||
|
||||
|
||||
public static string Version = string.Empty;
|
||||
public static GlamourerConfig Config = null!;
|
||||
|
||||
public static PenumbraAttach Penumbra = null!;
|
||||
|
||||
public Glamourer(DalamudPluginInterface pluginInterface)
|
||||
public static ICustomizationManager Customization = null!;
|
||||
public static RedrawManager RedrawManager = null!;
|
||||
public static RestrictedGear RestrictedGear = null!;
|
||||
private readonly WindowSystem _windowSystem = new("Glamourer");
|
||||
|
||||
private readonly Interface _interface;
|
||||
//public readonly DesignManager Designs;
|
||||
|
||||
//public static RevertableDesigns RevertableDesigns = new();
|
||||
//public readonly GlamourerIpc GlamourerIpc;
|
||||
|
||||
public unsafe Glamourer(DalamudPluginInterface pluginInterface)
|
||||
{
|
||||
Dalamud.Initialize(pluginInterface);
|
||||
Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "";
|
||||
Config = GlamourerConfig.Load();
|
||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
||||
Designs = new DesignManager();
|
||||
Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
|
||||
PlayerWatcher = PlayerWatchFactory.Create(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects);
|
||||
GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface);
|
||||
FixedDesignManager = new FixedDesignManager(Designs);
|
||||
if (!Config.ApplyFixedDesigns)
|
||||
PlayerWatcher.Disable();
|
||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
||||
RestrictedGear = GameData.RestrictedGear(Dalamud.GameData);
|
||||
var m = global::Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
||||
Config = GlamourerConfig.Load();
|
||||
Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
|
||||
|
||||
Dalamud.Commands.AddHandler("/glamourer", new CommandInfo(OnGlamourer)
|
||||
//Designs = new DesignManager();
|
||||
|
||||
//GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface);
|
||||
RedrawManager = new RedrawManager();
|
||||
|
||||
Dalamud.Commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer)
|
||||
{
|
||||
HelpMessage = "Open or close the Glamourer window.",
|
||||
});
|
||||
Dalamud.Commands.AddHandler("/glamour", new CommandInfo(OnGlamour)
|
||||
Dalamud.Commands.AddHandler(ApplyCommandString, new CommandInfo(OnGlamour)
|
||||
{
|
||||
HelpMessage = $"Use Glamourer Functions: {HelpString}",
|
||||
});
|
||||
|
||||
_interface = new Interface(this);
|
||||
_windowSystem.AddWindow(_interface);
|
||||
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
|
||||
var x = 0x00011000u;
|
||||
//FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x);
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
RedrawManager.Dispose();
|
||||
Penumbra.Dispose();
|
||||
Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
|
||||
_interface.Dispose();
|
||||
//GlamourerIpc.Dispose();
|
||||
Dalamud.Commands.RemoveHandler(ApplyCommandString);
|
||||
Dalamud.Commands.RemoveHandler(MainCommandString);
|
||||
}
|
||||
|
||||
public void OnGlamourer(string command, string arguments)
|
||||
=> _interface.ToggleVisibility();
|
||||
|
||||
private static GameObject? GetPlayer(string name)
|
||||
{
|
||||
var lowerName = name.ToLowerInvariant();
|
||||
return lowerName switch
|
||||
{
|
||||
"" => null,
|
||||
"<me>" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer,
|
||||
"self" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer,
|
||||
"<t>" => Dalamud.Targets.Target,
|
||||
"target" => Dalamud.Targets.Target,
|
||||
"<f>" => Dalamud.Targets.FocusTarget,
|
||||
"focus" => Dalamud.Targets.FocusTarget,
|
||||
"<mo>" => Dalamud.Targets.MouseOverTarget,
|
||||
"mouseover" => Dalamud.Targets.MouseOverTarget,
|
||||
_ => Dalamud.Objects.LastOrDefault(
|
||||
a => string.Equals(a.Name.ToString(), lowerName, StringComparison.InvariantCultureIgnoreCase)),
|
||||
};
|
||||
}
|
||||
|
||||
public void CopyToClipboard(Character player)
|
||||
{
|
||||
var save = new CharacterSave();
|
||||
save.LoadCharacter(player);
|
||||
ImGui.SetClipboardText(save.ToBase64());
|
||||
}
|
||||
|
||||
public void ApplyCommand(Character player, string target)
|
||||
{
|
||||
CharacterSave? save = null;
|
||||
if (target.ToLowerInvariant() == "clipboard")
|
||||
try
|
||||
{
|
||||
save = CharacterSave.FromString(ImGui.GetClipboardText());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Dalamud.Chat.PrintError("Clipboard does not contain a valid customization string.");
|
||||
}
|
||||
else if (!Designs.FileSystem.Find(target, out var child) || child is not Design d)
|
||||
Dalamud.Chat.PrintError("The given path to a saved design does not exist or does not point to a design.");
|
||||
else
|
||||
save = d.Data;
|
||||
|
||||
save?.Apply(player);
|
||||
Penumbra.UpdateCharacters(player);
|
||||
}
|
||||
|
||||
public void SaveCommand(Character player, string path)
|
||||
{
|
||||
var save = new CharacterSave();
|
||||
save.LoadCharacter(player);
|
||||
try
|
||||
{
|
||||
var (folder, name) = Designs.FileSystem.CreateAllFolders(path);
|
||||
var design = new Design(folder, name) { Data = save };
|
||||
folder.FindOrAddChild(design);
|
||||
Designs.Designs.Add(design.FullName(), design.Data);
|
||||
Designs.SaveToFile();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dalamud.Chat.PrintError("Could not save file:");
|
||||
Dalamud.Chat.PrintError($" {e.Message}");
|
||||
}
|
||||
}
|
||||
=> _interface.Toggle();
|
||||
|
||||
//private static GameObject? GetPlayer(string name)
|
||||
//{
|
||||
// var lowerName = name.ToLowerInvariant();
|
||||
// return lowerName switch
|
||||
// {
|
||||
// "" => null,
|
||||
// "<me>" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer,
|
||||
// "self" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer,
|
||||
// "<t>" => Dalamud.Targets.Target,
|
||||
// "target" => Dalamud.Targets.Target,
|
||||
// "<f>" => Dalamud.Targets.FocusTarget,
|
||||
// "focus" => Dalamud.Targets.FocusTarget,
|
||||
// "<mo>" => Dalamud.Targets.MouseOverTarget,
|
||||
// "mouseover" => Dalamud.Targets.MouseOverTarget,
|
||||
// _ => Dalamud.Objects.LastOrDefault(
|
||||
// a => string.Equals(a.Name.ToString(), lowerName, StringComparison.InvariantCultureIgnoreCase)),
|
||||
// };
|
||||
//}
|
||||
//
|
||||
//public void CopyToClipboard(Character player)
|
||||
//{
|
||||
// var save = new CharacterSave();
|
||||
// save.LoadCharacter(player);
|
||||
// ImGui.SetClipboardText(save.ToBase64());
|
||||
//}
|
||||
//
|
||||
//public void ApplyCommand(Character player, string target)
|
||||
//{
|
||||
// CharacterSave? save = null;
|
||||
// if (target.ToLowerInvariant() == "clipboard")
|
||||
// try
|
||||
// {
|
||||
// save = CharacterSave.FromString(ImGui.GetClipboardText());
|
||||
// }
|
||||
// catch (Exception)
|
||||
// {
|
||||
// Dalamud.Chat.PrintError("Clipboard does not contain a valid customization string.");
|
||||
// }
|
||||
// else if (!Designs.FileSystem.Find(target, out var child) || child is not Design d)
|
||||
// Dalamud.Chat.PrintError("The given path to a saved design does not exist or does not point to a design.");
|
||||
// else
|
||||
// save = d.Data;
|
||||
//
|
||||
// save?.Apply(player);
|
||||
// Penumbra.UpdateCharacters(player);
|
||||
//}
|
||||
//
|
||||
//public void SaveCommand(Character player, string path)
|
||||
//{
|
||||
// var save = new CharacterSave();
|
||||
// save.LoadCharacter(player);
|
||||
// try
|
||||
// {
|
||||
// var (folder, name) = Designs.FileSystem.CreateAllFolders(path);
|
||||
// var design = new Design(folder, name) { Data = save };
|
||||
// folder.FindOrAddChild(design);
|
||||
// Designs.Designs.Add(design.FullName(), design.Data);
|
||||
// Designs.SaveToFile();
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// Dalamud.Chat.PrintError("Could not save file:");
|
||||
// Dalamud.Chat.PrintError($" {e.Message}");
|
||||
// }
|
||||
//}
|
||||
//
|
||||
public void OnGlamour(string command, string arguments)
|
||||
{
|
||||
static void PrintHelp()
|
||||
|
|
@ -265,73 +176,62 @@ public class Glamourer : IDalamudPlugin
|
|||
Dalamud.Chat.Print($" {HelpString}");
|
||||
}
|
||||
|
||||
arguments = arguments.Trim();
|
||||
if (!arguments.Any())
|
||||
{
|
||||
PrintHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
var split = arguments.Split(new[]
|
||||
{
|
||||
',',
|
||||
}, 3, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (split.Length < 2)
|
||||
{
|
||||
PrintHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
var player = GetPlayer(split[1]) as Character;
|
||||
if (player == null)
|
||||
{
|
||||
Dalamud.Chat.Print($"Could not find object for {split[1]} or it was not a Character.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (split[0].ToLowerInvariant())
|
||||
{
|
||||
case "copy":
|
||||
CopyToClipboard(player);
|
||||
return;
|
||||
case "apply":
|
||||
{
|
||||
if (split.Length < 3)
|
||||
{
|
||||
Dalamud.Chat.Print("Applying requires a name for the save to be applied or 'clipboard'.");
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyCommand(player, split[2]);
|
||||
|
||||
return;
|
||||
}
|
||||
case "save":
|
||||
{
|
||||
if (split.Length < 3)
|
||||
{
|
||||
Dalamud.Chat.Print("Saving requires a name for the save.");
|
||||
return;
|
||||
}
|
||||
|
||||
SaveCommand(player, split[2]);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
PrintHelp();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
FixedDesignManager.Dispose();
|
||||
Penumbra.Dispose();
|
||||
PlayerWatcher.Dispose();
|
||||
_interface.Dispose();
|
||||
GlamourerIpc.Dispose();
|
||||
Dalamud.Commands.RemoveHandler("/glamour");
|
||||
Dalamud.Commands.RemoveHandler("/glamourer");
|
||||
//arguments = arguments.Trim();
|
||||
//if (!arguments.Any())
|
||||
//{
|
||||
// PrintHelp();
|
||||
// return;
|
||||
//}
|
||||
//
|
||||
//var split = arguments.Split(new[]
|
||||
//{
|
||||
// ',',
|
||||
//}, 3, StringSplitOptions.RemoveEmptyEntries);
|
||||
//
|
||||
//if (split.Length < 2)
|
||||
//{
|
||||
// PrintHelp();
|
||||
// return;
|
||||
//}
|
||||
//
|
||||
//var player = GetPlayer(split[1]) as Character;
|
||||
//if (player == null)
|
||||
//{
|
||||
// Dalamud.Chat.Print($"Could not find object for {split[1]} or it was not a Character.");
|
||||
// return;
|
||||
//}
|
||||
//
|
||||
//switch (split[0].ToLowerInvariant())
|
||||
//{
|
||||
// case "copy":
|
||||
// CopyToClipboard(player);
|
||||
// return;
|
||||
// case "apply":
|
||||
// {
|
||||
// if (split.Length < 3)
|
||||
// {
|
||||
// Dalamud.Chat.Print("Applying requires a name for the save to be applied or 'clipboard'.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// ApplyCommand(player, split[2]);
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
// case "save":
|
||||
// {
|
||||
// if (split.Length < 3)
|
||||
// {
|
||||
// Dalamud.Chat.Print("Saving requires a name for the save.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// SaveCommand(player, split[2]);
|
||||
// return;
|
||||
// }
|
||||
// default:
|
||||
// PrintHelp();
|
||||
// return;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,17 +77,12 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3">
|
||||
<Private>false</Private>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="System.Memory" Version="4.5.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
|
||||
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||
<ProjectReference Include="..\..\Penumbra\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -105,7 +100,17 @@
|
|||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
|
||||
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low">
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" />
|
||||
</Exec>
|
||||
|
||||
<PropertyGroup>
|
||||
<InformationalVersion>$(GitCommitHash)</InformationalVersion>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Glamourer.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
|
|||
464
Glamourer/Gui/Interface.Actors.cs
Normal file
464
Glamourer/Gui/Interface.Actors.cs
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Logging;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private class ActorTab
|
||||
{
|
||||
private ObjectManager.ActorData _data = new(string.Empty, string.Empty, Actor.Null, false, Actor.Null);
|
||||
private CharacterSave _character = new();
|
||||
private Actor _nextSelect = Actor.Null;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Actors");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
DrawActorSelector();
|
||||
if (_data.Label.Length == 0)
|
||||
return;
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (_data.Actor.IsHuman)
|
||||
DrawActorPanel();
|
||||
else
|
||||
DrawMonsterPanel();
|
||||
}
|
||||
|
||||
private void DrawActorPanel()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
if (DrawCustomization(_character.Customize, _character.Equipment, !_data.Modifiable))
|
||||
{
|
||||
Glamourer.RedrawManager.Set(_data.Actor.Address, _character);
|
||||
Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true);
|
||||
}
|
||||
|
||||
if (ImGui.Button("Set Machinist Goggles"))
|
||||
{
|
||||
Glamourer.RedrawManager.ChangeEquip(_data.Actor.Address, EquipSlot.Head, new CharacterArmor(265, 1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMonsterPanel()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
var currentModel = (uint)_data.Actor.ModelId;
|
||||
var models = GameData.Models(Dalamud.GameData);
|
||||
var currentData = models.Models.TryGetValue(currentModel, out var c) ? c.FirstName : $"#{currentModel}";
|
||||
using var combo = ImRaii.Combo("Model Id", currentData);
|
||||
if (!combo)
|
||||
return;
|
||||
|
||||
foreach (var (id, data) in models.Models)
|
||||
{
|
||||
if (ImGui.Selectable(data.FirstName, id == currentModel) || id == currentModel)
|
||||
{
|
||||
_data.Actor.SetModelId((int)id);
|
||||
Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true);
|
||||
}
|
||||
ImGuiUtil.HoverTooltip(data.AllNames);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private LowerString _actorFilter = LowerString.Empty;
|
||||
|
||||
private void DrawActorSelector()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
var oldSpacing = ImGui.GetStyle().ItemSpacing;
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
ImGui.SetNextItemWidth(_actorSelectorWidth);
|
||||
LowerString.InputWithHint("##actorFilter", "Filter...", ref _actorFilter, 64);
|
||||
using (var child = ImRaii.Child("##actorSelector", new Vector2(_actorSelectorWidth, -ImGui.GetFrameHeight()), true))
|
||||
{
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
_data.Actor = Actor.Null;
|
||||
_data.GPose = Actor.Null;
|
||||
_data.Modifiable = false;
|
||||
|
||||
style.Push(ImGuiStyleVar.ItemSpacing, oldSpacing);
|
||||
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
|
||||
var remainder = ImGuiClip.FilteredClippedDraw(ObjectManager.GetEnumerator(), skips, CheckFilter, DrawSelectable);
|
||||
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
|
||||
style.Pop();
|
||||
}
|
||||
|
||||
DrawSelectionButtons();
|
||||
}
|
||||
|
||||
private void UpdateSelection(ObjectManager.ActorData data)
|
||||
{
|
||||
_data = data;
|
||||
_character.Load(_data.Actor);
|
||||
}
|
||||
|
||||
private bool CheckFilter(ObjectManager.ActorData data)
|
||||
{
|
||||
if (_nextSelect && _nextSelect == data.Actor || data.Label == _data.Label)
|
||||
UpdateSelection(data);
|
||||
return data.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void DrawSelectable(ObjectManager.ActorData data)
|
||||
{
|
||||
var equal = data.Label == _data.Label;
|
||||
if (ImGui.Selectable(data.Label, equal) && !equal)
|
||||
UpdateSelection(data);
|
||||
}
|
||||
|
||||
private void DrawSelectionButtons()
|
||||
{
|
||||
_nextSelect = Actor.Null;
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
var buttonWidth = new Vector2(_actorSelectorWidth / 2, 0);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
|
||||
, "Select the local player character.", !ObjectManager.Player, true))
|
||||
_nextSelect = _inGPose ? ObjectManager.GPosePlayer : ObjectManager.Player;
|
||||
ImGui.SameLine();
|
||||
Actor targetActor = Dalamud.Targets.Target?.Address;
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth,
|
||||
"Select the current target, if it is in the list.", _inGPose || !targetActor, true))
|
||||
_nextSelect = targetActor;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ActorTab _actorTab = new();
|
||||
}
|
||||
|
||||
//internal partial class Interface
|
||||
//{
|
||||
// private readonly CharacterSave _currentSave = new();
|
||||
// private string _newDesignName = string.Empty;
|
||||
// private bool _keyboardFocus;
|
||||
// private bool _holdShift;
|
||||
// private bool _holdCtrl;
|
||||
// private const string DesignNamePopupLabel = "Save Design As...";
|
||||
// private const uint RedHeaderColor = 0xFF1818C0;
|
||||
// private const uint GreenHeaderColor = 0xFF18C018;
|
||||
//
|
||||
// private void DrawPlayerHeader()
|
||||
// {
|
||||
// var color = _player == null ? RedHeaderColor : GreenHeaderColor;
|
||||
// var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||
// using var c = ImRaii.PushColor(ImGuiCol.Text, color)
|
||||
// .Push(ImGuiCol.Button, buttonColor)
|
||||
// .Push(ImGuiCol.ButtonHovered, buttonColor)
|
||||
// .Push(ImGuiCol.ButtonActive, buttonColor);
|
||||
// using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
// .Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
// ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f);
|
||||
// }
|
||||
//
|
||||
// private static void DrawCopyClipboardButton(CharacterSave save)
|
||||
// {
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString()))
|
||||
// ImGui.SetClipboardText(save.ToBase64());
|
||||
// ImGui.PopFont();
|
||||
// ImGuiUtil.HoverTooltip("Copy customization code to clipboard.");
|
||||
// }
|
||||
//
|
||||
// private static unsafe void ConditionalApply(CharacterSave save, FFXIVClientStructs.FFXIV.Client.Game.Character.Character* player)
|
||||
// {
|
||||
// //if (ImGui.GetIO().KeyShift)
|
||||
// // save.ApplyOnlyCustomizations(player);
|
||||
// //else if (ImGui.GetIO().KeyCtrl)
|
||||
// // save.ApplyOnlyEquipment(player);
|
||||
// //else
|
||||
// // save.Apply(player);
|
||||
// }
|
||||
//
|
||||
// private static unsafe void ConditionalApply(CharacterSave save, Character player)
|
||||
// => ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player.Address);
|
||||
//
|
||||
// private static CharacterSave ConditionalCopy(CharacterSave save, bool shift, bool ctrl)
|
||||
// {
|
||||
// var copy = save.Copy();
|
||||
// if (shift)
|
||||
// {
|
||||
// copy.Load(new CharacterEquipment());
|
||||
// copy.SetHatState = false;
|
||||
// copy.SetVisorState = false;
|
||||
// copy.SetWeaponState = false;
|
||||
// copy.WriteEquipment = CharacterEquipMask.None;
|
||||
// }
|
||||
// else if (ctrl)
|
||||
// {
|
||||
// copy.Load(CharacterCustomization.Default);
|
||||
// copy.SetHatState = false;
|
||||
// copy.SetVisorState = false;
|
||||
// copy.SetWeaponState = false;
|
||||
// copy.WriteCustomizations = false;
|
||||
// }
|
||||
//
|
||||
// return copy;
|
||||
// }
|
||||
//
|
||||
// private bool DrawApplyClipboardButton()
|
||||
// {
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()) && _player != null;
|
||||
// ImGui.PopFont();
|
||||
// ImGuiUtil.HoverTooltip(
|
||||
// "Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment.");
|
||||
//
|
||||
// if (!applyButton)
|
||||
// return false;
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// var text = ImGui.GetClipboardText();
|
||||
// if (!text.Any())
|
||||
// return false;
|
||||
//
|
||||
// var save = CharacterSave.FromString(text);
|
||||
// ConditionalApply(save, _player!);
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Information($"{e}");
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// private void DrawSaveDesignButton()
|
||||
// {
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// if (ImGui.Button(FontAwesomeIcon.Save.ToIconString()))
|
||||
// OpenDesignNamePopup(DesignNameUse.SaveCurrent);
|
||||
//
|
||||
// ImGui.PopFont();
|
||||
// ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment.");
|
||||
//
|
||||
// DrawDesignNamePopup(DesignNameUse.SaveCurrent);
|
||||
// }
|
||||
//
|
||||
// private void DrawTargetPlayerButton()
|
||||
// {
|
||||
// if (ImGui.Button("Target Player"))
|
||||
// Dalamud.Targets.SetTarget(_player);
|
||||
// }
|
||||
//
|
||||
// private unsafe void DrawApplyToPlayerButton(CharacterSave save)
|
||||
// {
|
||||
// if (!ImGui.Button("Apply to Self"))
|
||||
// return;
|
||||
//
|
||||
// var player = _inGPose
|
||||
// ? (Character?)Dalamud.Objects[GPoseObjectId]
|
||||
// : Dalamud.ClientState.LocalPlayer;
|
||||
// var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null;
|
||||
// if (player == null)
|
||||
// return;
|
||||
//
|
||||
// ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player.Address);
|
||||
// if (_inGPose)
|
||||
// ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)fallback!.Address);
|
||||
// Glamourer.Penumbra.UpdateCharacters(player, fallback);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// private static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.Character* TransformToCustomizable(
|
||||
// FFXIVClientStructs.FFXIV.Client.Game.Character.Character* actor)
|
||||
// {
|
||||
// if (actor == null)
|
||||
// return null;
|
||||
//
|
||||
// if (actor->ModelCharaId == 0)
|
||||
// return actor;
|
||||
//
|
||||
// actor->ModelCharaId = 0;
|
||||
// CharacterCustomization.Default.Write(actor);
|
||||
// return actor;
|
||||
// }
|
||||
//
|
||||
// private static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Convert(GameObject? actor)
|
||||
// {
|
||||
// return actor switch
|
||||
// {
|
||||
// null => null,
|
||||
// PlayerCharacter p => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)p.Address,
|
||||
// BattleChara b => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)b.Address,
|
||||
// _ => actor.ObjectKind switch
|
||||
// {
|
||||
// ObjectKind.BattleNpc => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
|
||||
// ObjectKind.Companion => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
|
||||
// ObjectKind.Retainer => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
|
||||
// ObjectKind.EventNpc => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
|
||||
// _ => null,
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// private unsafe void DrawApplyToTargetButton(CharacterSave save)
|
||||
// {
|
||||
// if (!ImGui.Button("Apply to Target"))
|
||||
// return;
|
||||
//
|
||||
// var player = TransformToCustomizable(Convert(Dalamud.Targets.Target));
|
||||
// if (player == null)
|
||||
// return;
|
||||
//
|
||||
// var fallBackCharacter = _gPoseActors.TryGetValue(new Utf8String(player->GameObject.Name).ToString(), out var f) ? f : null;
|
||||
// ConditionalApply(save, player);
|
||||
// if (fallBackCharacter != null)
|
||||
// ConditionalApply(save, fallBackCharacter!);
|
||||
// //Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter);
|
||||
// }
|
||||
//
|
||||
// private void DrawRevertButton()
|
||||
// {
|
||||
// if (!ImGuiUtil.DrawDisabledButton("Revert", Vector2.Zero, string.Empty, _player == null))
|
||||
// return;
|
||||
//
|
||||
// Glamourer.RevertableDesigns.Revert(_player!);
|
||||
// var fallBackCharacter = _gPoseActors.TryGetValue(_player!.Name.ToString(), out var f) ? f : null;
|
||||
// if (fallBackCharacter != null)
|
||||
// Glamourer.RevertableDesigns.Revert(fallBackCharacter);
|
||||
// Glamourer.Penumbra.UpdateCharacters(_player, fallBackCharacter);
|
||||
// }
|
||||
//
|
||||
// private void SaveNewDesign(CharacterSave save)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName);
|
||||
// if (!name.Any())
|
||||
// return;
|
||||
//
|
||||
// var newDesign = new Design(folder, name) { Data = save };
|
||||
// folder.AddChild(newDesign);
|
||||
// _designs.Designs[newDesign.FullName()] = save;
|
||||
// _designs.SaveToFile();
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Error($"Could not save new design {_newDesignName}:\n{e}");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private unsafe void DrawMonsterPanel()
|
||||
// {
|
||||
// if (DrawApplyClipboardButton())
|
||||
// Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
//
|
||||
// ImGui.SameLine();
|
||||
// if (ImGui.Button("Convert to Character"))
|
||||
// {
|
||||
// //TransformToCustomizable(_player);
|
||||
// _currentLabel = _currentLabel.Replace("(Monster)", "(NPC)");
|
||||
// Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
// }
|
||||
//
|
||||
// if (!_inGPose)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// DrawTargetPlayerButton();
|
||||
// }
|
||||
//
|
||||
// var currentModel = ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player!.Address)->ModelCharaId;
|
||||
// using var combo = ImRaii.Combo("Model Id", currentModel.ToString());
|
||||
// if (!combo)
|
||||
// return;
|
||||
//
|
||||
// foreach (var (id, _) in _models.Skip(1))
|
||||
// {
|
||||
// if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel)
|
||||
// continue;
|
||||
//
|
||||
// ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player!.Address)->ModelCharaId = 0;
|
||||
// Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void DrawPlayerPanel()
|
||||
// {
|
||||
// DrawCopyClipboardButton(_currentSave);
|
||||
// ImGui.SameLine();
|
||||
// var changes = !_currentSave.WriteProtected && DrawApplyClipboardButton();
|
||||
// ImGui.SameLine();
|
||||
// DrawSaveDesignButton();
|
||||
// ImGui.SameLine();
|
||||
// DrawApplyToPlayerButton(_currentSave);
|
||||
// if (!_inGPose)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// DrawApplyToTargetButton(_currentSave);
|
||||
// if (_player != null && !_currentSave.WriteProtected)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// DrawTargetPlayerButton();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var data = _currentSave;
|
||||
// if (!_currentSave.WriteProtected)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// DrawRevertButton();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
|
||||
// data = data.Copy();
|
||||
// }
|
||||
//
|
||||
// if (DrawCustomization(ref data.Customizations) && _player != null)
|
||||
// {
|
||||
// Glamourer.RevertableDesigns.Add(_player);
|
||||
// _currentSave.Customizations.Write(_player.Address);
|
||||
// changes = true;
|
||||
// }
|
||||
//
|
||||
// changes |= DrawEquip(data.Equipment);
|
||||
// changes |= DrawMiscellaneous(data, _player);
|
||||
//
|
||||
// if (_player != null && changes)
|
||||
// Glamourer.Penumbra.UpdateCharacters(_player);
|
||||
// if (_currentSave.WriteProtected)
|
||||
// ImGui.PopStyleVar();
|
||||
// }
|
||||
//
|
||||
// private unsafe void DrawActorPanel()
|
||||
// {
|
||||
// using var group = ImRaii.Group();
|
||||
// DrawPlayerHeader();
|
||||
// using var child = ImRaii.Child("##playerData", -Vector2.One, true);
|
||||
// if (!child)
|
||||
// return;
|
||||
//
|
||||
// if (_player == null || ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player.Address)->ModelCharaId == 0)
|
||||
// DrawPlayerPanel();
|
||||
// else
|
||||
// DrawMonsterPanel();
|
||||
// }
|
||||
//}
|
||||
418
Glamourer/Gui/Interface.Customization.cs
Normal file
418
Glamourer/Gui/Interface.Customization.cs
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private static byte _tempStorage;
|
||||
private static CustomizationId _tempType;
|
||||
|
||||
private static bool DrawCustomization(CharacterCustomization customize, CharacterEquip equip, bool locked)
|
||||
{
|
||||
if (!ImGui.CollapsingHeader("Character Customization"))
|
||||
return false;
|
||||
|
||||
var ret = DrawRaceGenderSelector(customize, equip, locked);
|
||||
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
|
||||
|
||||
foreach (var id in set.Order[CharaMakeParams.MenuType.Percentage])
|
||||
ret |= PercentageSelector(set, id, customize, locked);
|
||||
|
||||
Functions.IteratePairwise(set.Order[CharaMakeParams.MenuType.IconSelector], c => DrawIconSelector(set, c, customize, locked),
|
||||
ImGui.SameLine);
|
||||
|
||||
ret |= DrawMultiIconSelector(set, customize, locked);
|
||||
|
||||
foreach (var id in set.Order[CharaMakeParams.MenuType.ListSelector])
|
||||
ret |= DrawListSelector(set, id, customize, locked);
|
||||
|
||||
Functions.IteratePairwise(set.Order[CharaMakeParams.MenuType.ColorPicker], c => DrawColorPicker(set, c, customize, locked),
|
||||
ImGui.SameLine);
|
||||
|
||||
ret |= Checkbox(set.Option(CustomizationId.HighlightsOnFlag), customize.HighlightsOn, b => customize.HighlightsOn = b, locked);
|
||||
var xPos = _inputIntSize + _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||
ImGui.SameLine(xPos);
|
||||
ret |= Checkbox($"{Glamourer.Customization.GetName(CustomName.Reverse)} {set.Option(CustomizationId.FacePaint)}",
|
||||
customize.FacePaintReversed, b => customize.FacePaintReversed = b, locked);
|
||||
ret |= Checkbox($"{Glamourer.Customization.GetName(CustomName.IrisSmall)} {Glamourer.Customization.GetName(CustomName.IrisSize)}",
|
||||
customize.SmallIris, b => customize.SmallIris = b, locked);
|
||||
|
||||
if (customize.Race != Race.Hrothgar)
|
||||
{
|
||||
ImGui.SameLine(xPos);
|
||||
ret |= Checkbox(set.Option(CustomizationId.LipColor), customize.Lipstick, b => customize.Lipstick = b, locked);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawRaceGenderSelector(CharacterCustomization customize, CharacterEquip equip, bool locked)
|
||||
{
|
||||
var ret = DrawGenderSelector(customize, equip, locked);
|
||||
ImGui.SameLine();
|
||||
using var group = ImRaii.Group();
|
||||
ret |= DrawRaceCombo(customize, equip, locked);
|
||||
var gender = Glamourer.Customization.GetName(CustomName.Gender);
|
||||
var clan = Glamourer.Customization.GetName(CustomName.Clan);
|
||||
ImGui.TextUnformatted($"{gender} & {clan}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawGenderSelector(CharacterCustomization customize, CharacterEquip equip, bool locked)
|
||||
{
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
var icon = customize.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
|
||||
var restricted = customize.Race == Race.Hrothgar;
|
||||
if (restricted)
|
||||
icon = FontAwesomeIcon.MarsDouble;
|
||||
|
||||
if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, restricted || locked, true))
|
||||
return false;
|
||||
|
||||
var gender = customize.Gender == Gender.Male ? Gender.Female : Gender.Male;
|
||||
return customize.ChangeGender(gender, locked ? CharacterEquip.Null : equip);
|
||||
}
|
||||
|
||||
private static bool DrawRaceCombo(CharacterCustomization customize, CharacterEquip equip, bool locked)
|
||||
{
|
||||
using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked);
|
||||
ImGui.SetNextItemWidth(_raceSelectorWidth);
|
||||
using var combo = ImRaii.Combo("##subRaceCombo", customize.ClanName());
|
||||
if (!combo)
|
||||
return false;
|
||||
|
||||
if (locked)
|
||||
ImGui.CloseCurrentPopup();
|
||||
|
||||
var ret = false;
|
||||
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
|
||||
{
|
||||
if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, customize.Gender), subRace == customize.Clan))
|
||||
ret |= customize.ChangeRace(subRace, equip);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool Checkbox(string label, bool current, Action<bool> setter, bool locked)
|
||||
{
|
||||
var tmp = current;
|
||||
var ret = false;
|
||||
using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked);
|
||||
if (ImGui.Checkbox($"##{label}", ref tmp) && tmp == current && !locked)
|
||||
{
|
||||
setter(tmp);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
alpha.Pop();
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(label);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool PercentageSelector(CustomizationSet set, CustomizationId id, CharacterCustomization customization, bool locked)
|
||||
{
|
||||
using var bigGroup = ImRaii.Group();
|
||||
using var _ = ImRaii.PushId((int)id);
|
||||
int value = id == _tempType ? _tempStorage : customization[id];
|
||||
var count = set.Count(id);
|
||||
ImGui.SetNextItemWidth(_comboSelectorSize);
|
||||
|
||||
var (min, max) = locked ? (value, value) : (0, count - 1);
|
||||
using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked);
|
||||
if (ImGui.SliderInt("##slider", ref value, min, max, string.Empty, ImGuiSliderFlags.AlwaysClamp) && !locked)
|
||||
{
|
||||
_tempStorage = (byte)value;
|
||||
_tempType = id;
|
||||
}
|
||||
|
||||
var ret = ImGui.IsItemDeactivatedAfterEdit();
|
||||
|
||||
ImGui.SameLine();
|
||||
ret |= InputInt("##input", id, --value, min, max, locked);
|
||||
|
||||
alpha.Pop();
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(set.OptionName[(int)id]);
|
||||
|
||||
if (ret)
|
||||
customization[id] = _tempStorage;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool InputInt(string label, CustomizationId id, int startValue, int minValue, int maxValue, bool locked)
|
||||
{
|
||||
var tmp = startValue + 1;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt(label, ref tmp, 1, 1, ImGuiInputTextFlags.EnterReturnsTrue)
|
||||
&& !locked
|
||||
&& tmp != startValue + 1
|
||||
&& tmp >= minValue
|
||||
&& tmp <= maxValue)
|
||||
{
|
||||
_tempType = id;
|
||||
_tempStorage = (byte)(tmp - 1);
|
||||
}
|
||||
|
||||
var ret = ImGui.IsItemDeactivatedAfterEdit() && !locked;
|
||||
if (!locked)
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [{minValue}, {maxValue}]");
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawIconSelector(CustomizationSet set, CustomizationId id, CharacterCustomization customize, bool locked)
|
||||
{
|
||||
const string popupName = "Style Picker";
|
||||
|
||||
using var bigGroup = ImRaii.Group();
|
||||
using var _ = ImRaii.PushId((int)id);
|
||||
var count = set.Count(id);
|
||||
var label = set.Option(id);
|
||||
|
||||
var current = set.DataByValue(id, _tempType == id ? _tempStorage : customize[id], out var custom);
|
||||
if (current < 0)
|
||||
{
|
||||
label = $"{label} (Custom #{customize[id]})";
|
||||
current = 0;
|
||||
custom = set.Data(id, 0);
|
||||
}
|
||||
|
||||
var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId);
|
||||
using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked);
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize) && !locked)
|
||||
ImGui.OpenPopup(popupName);
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
ImGui.SameLine();
|
||||
using var group = ImRaii.Group();
|
||||
var (min, max) = locked ? (current, current) : (1, count);
|
||||
var ret = InputInt("##text", id, current, min, max, locked);
|
||||
if (ret)
|
||||
customize[id] = set.Data(id, _tempStorage).Value;
|
||||
|
||||
ImGui.TextUnformatted($"{label} ({custom.Value.Value})");
|
||||
|
||||
ret |= DrawIconPickerPopup(popupName, set, id, customize);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawIconPickerPopup(string label, CustomizationSet set, CustomizationId id, CharacterCustomization customize)
|
||||
{
|
||||
using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize);
|
||||
if (!popup)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
var count = set.Count(id);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var custom = set.Data(id, i);
|
||||
var icon = Glamourer.Customization.GetIcon(custom.IconId);
|
||||
using var group = ImRaii.Group();
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||
{
|
||||
customize[id] = custom.Value;
|
||||
ret = true;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
var text = custom.Value.ToString();
|
||||
var textWidth = ImGui.CalcTextSize(text).X;
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (_iconSize.X - textWidth + 2 * ImGui.GetStyle().FramePadding.X) / 2);
|
||||
ImGui.TextUnformatted(text);
|
||||
group.Dispose();
|
||||
|
||||
if (i % 8 != 7)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawColorPicker(CustomizationSet set, CustomizationId id, CharacterCustomization customize, bool locked)
|
||||
{
|
||||
const string popupName = "Color Picker";
|
||||
using var _ = ImRaii.PushId((int)id);
|
||||
var ret = false;
|
||||
var count = set.Count(id);
|
||||
var label = set.Option(id);
|
||||
var (current, custom) = GetCurrentCustomization(set, id, customize);
|
||||
|
||||
using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked);
|
||||
if (ImGui.ColorButton($"{current + 1}##color", ImGui.ColorConvertU32ToFloat4(custom.Color), ImGuiColorEditFlags.None, _framedIconSize)
|
||||
&& !locked)
|
||||
ImGui.OpenPopup(popupName);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
using (var group = ImRaii.Group())
|
||||
{
|
||||
var (min, max) = locked ? (current, current) : (1, count);
|
||||
if (InputInt("##text", id, current, min, max, locked))
|
||||
{
|
||||
customize[id] = set.Data(id, current).Value;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(label);
|
||||
}
|
||||
|
||||
return ret | DrawColorPickerPopup(popupName, set, id, customize);
|
||||
}
|
||||
|
||||
private static (int, Customization.Customization) GetCurrentCustomization(CustomizationSet set, CustomizationId id,
|
||||
CharacterCustomization customize)
|
||||
{
|
||||
var current = set.DataByValue(id, customize[id], out var custom);
|
||||
if (set.IsAvailable(id) && current < 0)
|
||||
{
|
||||
PluginLog.Warning($"Read invalid customization value {customize[id]} for {id}.");
|
||||
current = 0;
|
||||
custom = set.Data(id, 0);
|
||||
}
|
||||
|
||||
return (current, custom!.Value);
|
||||
}
|
||||
|
||||
private static bool DrawColorPickerPopup(string label, CustomizationSet set, CustomizationId id, CharacterCustomization customize)
|
||||
{
|
||||
using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize);
|
||||
if (!popup)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
var count = set.Count(id);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var custom = set.Data(id, i);
|
||||
if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
|
||||
{
|
||||
customize[id] = custom.Value;
|
||||
ret = true;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
if (i % 8 != 7)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMultiIconSelector(CustomizationSet set, CharacterCustomization customize, bool locked)
|
||||
{
|
||||
using var bigGroup = ImRaii.Group();
|
||||
using var _ = ImRaii.PushId((int)CustomizationId.FacialFeaturesTattoos);
|
||||
using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked);
|
||||
var ret = DrawMultiIcons(set, customize, locked);
|
||||
ImGui.SameLine();
|
||||
using var group = ImRaii.Group();
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetTextLineHeightWithSpacing() + 3 * ImGui.GetStyle().ItemSpacing.Y / 2);
|
||||
int value = customize[CustomizationId.FacialFeaturesTattoos];
|
||||
var (min, max) = locked ? (value, value) : (1, 256);
|
||||
if (InputInt(string.Empty, CustomizationId.FacialFeaturesTattoos, value, min, max, locked))
|
||||
{
|
||||
customize[CustomizationId.FacialFeaturesTattoos] = (byte)value;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(set.Option(CustomizationId.FacialFeaturesTattoos));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawMultiIcons(CustomizationSet set, CharacterCustomization customize, bool locked)
|
||||
{
|
||||
using var _ = ImRaii.Group();
|
||||
var face = customize.Face;
|
||||
if (set.Faces.Count < face)
|
||||
face = 1;
|
||||
|
||||
var ret = false;
|
||||
var count = set.Count(CustomizationId.FacialFeaturesTattoos);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var enabled = customize.FacialFeature(i);
|
||||
var feature = set.FacialFeature(face, i);
|
||||
var icon = i == count - 1
|
||||
? LegacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
|
||||
: Glamourer.Customization.GetIcon(feature.IconId);
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
|
||||
Vector4.Zero, enabled ? Vector4.One : RedTint)
|
||||
&& !locked)
|
||||
{
|
||||
customize.FacialFeature(i, !enabled);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 1f, !locked);
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
if (i % 4 != 3)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static bool DrawListSelector(CustomizationSet set, CustomizationId id, CharacterCustomization customize, bool locked)
|
||||
{
|
||||
using var _ = ImRaii.PushId((int)id);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
var ret = false;
|
||||
int current = customize[id];
|
||||
var count = set.Count(id);
|
||||
|
||||
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
||||
using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked);
|
||||
using (var combo = ImRaii.Combo("##combo", $"{set.Option(id)} #{current + 1}"))
|
||||
{
|
||||
if (combo)
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
if (!ImGui.Selectable($"{set.Option(id)} #{i + 1}##combo", i == current) || i == current || locked)
|
||||
continue;
|
||||
|
||||
customize[id] = (byte)i;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var (min, max) = locked ? (current, current) : (1, count);
|
||||
if (InputInt("##text", id, current, min, max, locked))
|
||||
{
|
||||
customize[id] = (byte)current;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
alpha.Pop();
|
||||
ImGui.TextUnformatted(set.Option(id));
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
104
Glamourer/Gui/Interface.SettingsTab.cs
Normal file
104
Glamourer/Gui/Interface.SettingsTab.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private static void Checkmark(string label, string tooltip, bool value, Action<bool> setter)
|
||||
{
|
||||
if (ImGuiUtil.Checkbox(label, tooltip, value, setter))
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
private static void ChangeAndSave<T>(T value, T currentValue, Action<T> setter) where T : IEquatable<T>
|
||||
{
|
||||
if (value.Equals(currentValue))
|
||||
return;
|
||||
|
||||
setter(value);
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
private static void DrawColorPicker(string name, string tooltip, uint value, uint defaultValue, Action<uint> setter)
|
||||
{
|
||||
const ImGuiColorEditFlags flags = ImGuiColorEditFlags.AlphaPreviewHalf | ImGuiColorEditFlags.NoInputs;
|
||||
|
||||
var tmp = ImGui.ColorConvertU32ToFloat4(value);
|
||||
if (ImGui.ColorEdit4($"##{name}", ref tmp, flags))
|
||||
ChangeAndSave(ImGui.ColorConvertFloat4ToU32(tmp), value, setter);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button($"Default##{name}"))
|
||||
ChangeAndSave(defaultValue, value, setter);
|
||||
ImGuiUtil.HoverTooltip(
|
||||
$"Reset to default: #{defaultValue & 0xFF:X2}{(defaultValue >> 8) & 0xFF:X2}{(defaultValue >> 16) & 0xFF:X2}{defaultValue >> 24:X2}");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(name);
|
||||
ImGuiUtil.HoverTooltip(tooltip);
|
||||
}
|
||||
|
||||
private static void DrawRestorePenumbraButton()
|
||||
{
|
||||
const string buttonLabel = "Re-Register Penumbra";
|
||||
if (!Glamourer.Config.AttachToPenumbra)
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f);
|
||||
ImGui.Button(buttonLabel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui.Button(buttonLabel))
|
||||
Glamourer.Penumbra.Reattach(true);
|
||||
|
||||
ImGuiUtil.HoverTooltip(
|
||||
"If Penumbra did not register the functions for some reason, pressing this button might help restore functionality.");
|
||||
}
|
||||
|
||||
private static void DrawSettingsTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Settings");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
var cfg = Glamourer.Config;
|
||||
ImGui.Dummy(_spacing);
|
||||
|
||||
Checkmark("Folders First", "Sort Folders before all designs instead of lexicographically.", cfg.FoldersFirst,
|
||||
v => cfg.FoldersFirst = v);
|
||||
Checkmark("Color Designs", "Color the names of designs in the selector using the colors from below for the given cases.",
|
||||
cfg.ColorDesigns,
|
||||
v => cfg.ColorDesigns = v);
|
||||
Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", cfg.ShowLocks,
|
||||
v => cfg.ShowLocks = v);
|
||||
Checkmark("Attach to Penumbra",
|
||||
"Allows you to right-click items in the Changed Items tab of a mod in Penumbra to apply them to your player character.",
|
||||
cfg.AttachToPenumbra,
|
||||
v =>
|
||||
{
|
||||
cfg.AttachToPenumbra = v;
|
||||
if (v)
|
||||
Glamourer.Penumbra.Reattach(true);
|
||||
else
|
||||
Glamourer.Penumbra.Unattach();
|
||||
});
|
||||
ImGui.SameLine();
|
||||
DrawRestorePenumbraButton();
|
||||
|
||||
Checkmark("Apply Fixed Designs",
|
||||
"Automatically apply fixed designs to characters and redraw them when anything changes.",
|
||||
cfg.ApplyFixedDesigns,
|
||||
v => { cfg.ApplyFixedDesigns = v; });
|
||||
|
||||
ImGui.Dummy(_spacing);
|
||||
|
||||
DrawColorPicker("Customization Color", "The color for designs that only apply their character customization.",
|
||||
cfg.CustomizationColor, GlamourerConfig.DefaultCustomizationColor, c => cfg.CustomizationColor = c);
|
||||
DrawColorPicker("Equipment Color", "The color for designs that only apply some or all of their equipment slots and stains.",
|
||||
cfg.EquipmentColor, GlamourerConfig.DefaultEquipmentColor, c => cfg.EquipmentColor = c);
|
||||
DrawColorPicker("State Color", "The color for designs that only apply some state modification.",
|
||||
cfg.StateColor, GlamourerConfig.DefaultStateColor, c => cfg.StateColor = c);
|
||||
}
|
||||
}
|
||||
58
Glamourer/Gui/Interface.State.cs
Normal file
58
Glamourer/Gui/Interface.State.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private static readonly ImGuiScene.TextureWrap? LegacyTattoo = GetLegacyTattooIcon();
|
||||
private static readonly Vector4 RedTint = new(0.6f, 0.3f, 0.3f, 1f);
|
||||
|
||||
|
||||
private static Vector2 _iconSize = Vector2.Zero;
|
||||
private static Vector2 _framedIconSize = Vector2.Zero;
|
||||
private static Vector2 _spacing = Vector2.Zero;
|
||||
private static float _actorSelectorWidth;
|
||||
private static float _inputIntSize;
|
||||
private static float _comboSelectorSize;
|
||||
private static float _raceSelectorWidth;
|
||||
private static bool _inGPose;
|
||||
|
||||
|
||||
private static void UpdateState()
|
||||
{
|
||||
// General
|
||||
_inGPose = ObjectManager.IsInGPose();
|
||||
_spacing = _spacing with { Y = ImGui.GetTextLineHeightWithSpacing() / 2 };
|
||||
_actorSelectorWidth = 200 * ImGuiHelpers.GlobalScale;
|
||||
|
||||
// Customize
|
||||
_iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2);
|
||||
_framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
|
||||
_inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X;
|
||||
_comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
|
||||
|
||||
// _itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1;
|
||||
}
|
||||
|
||||
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon()
|
||||
{
|
||||
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
|
||||
if (resource != null)
|
||||
{
|
||||
var rawImage = new byte[resource.Length];
|
||||
var length = resource.Read(rawImage, 0, (int)resource.Length);
|
||||
if (length != resource.Length)
|
||||
return null;
|
||||
|
||||
return Dalamud.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +1,139 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Glamourer.Designs;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using OtterGui.Widgets;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface : IDisposable
|
||||
internal partial class Interface : Window, IDisposable
|
||||
{
|
||||
public const float SelectorWidth = 200;
|
||||
public const float MinWindowWidth = 675;
|
||||
public const int GPoseObjectId = 201;
|
||||
private const string PluginName = "Glamourer";
|
||||
private readonly string _glamourerHeader;
|
||||
|
||||
private readonly IReadOnlyDictionary<byte, Stain> _stains;
|
||||
private readonly IReadOnlyDictionary<uint, ModelChara> _models;
|
||||
private readonly IObjectIdentifier _identifier;
|
||||
private readonly Dictionary<EquipSlot, (ComboWithFilter<Item>, ComboWithFilter<Stain>)> _combos;
|
||||
private readonly ImGuiScene.TextureWrap? _legacyTattooIcon;
|
||||
private readonly Dictionary<EquipSlot, string> _equipSlotNames;
|
||||
private readonly DesignManager _designs;
|
||||
private readonly Glamourer _plugin;
|
||||
|
||||
private bool _visible;
|
||||
private bool _inGPose;
|
||||
private readonly Glamourer _plugin;
|
||||
|
||||
public Interface(Glamourer plugin)
|
||||
: base(GetLabel())
|
||||
{
|
||||
_plugin = plugin;
|
||||
_designs = plugin.Designs;
|
||||
_glamourerHeader = Glamourer.Version.Length > 0
|
||||
? $"{PluginName} v{Glamourer.Version}###{PluginName}Main"
|
||||
: $"{PluginName}###{PluginName}Main";
|
||||
_plugin = plugin;
|
||||
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||
Dalamud.PluginInterface.UiBuilder.Draw += Draw;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += ToggleVisibility;
|
||||
|
||||
_equipSlotNames = GetEquipSlotNames();
|
||||
|
||||
_stains = GameData.Stains(Dalamud.GameData);
|
||||
_models = GameData.Models(Dalamud.GameData);
|
||||
_identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
||||
|
||||
|
||||
var stainCombo = CreateDefaultStainCombo(_stains.Values.ToArray());
|
||||
|
||||
var equip = GameData.ItemsBySlot(Dalamud.GameData);
|
||||
_combos = equip.ToDictionary(kvp => kvp.Key, kvp => CreateCombos(kvp.Key, kvp.Value, stainCombo));
|
||||
_legacyTattooIcon = GetLegacyTattooIcon();
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += Toggle;
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
{
|
||||
MinimumSize = new Vector2(675, 675),
|
||||
MaximumSize = ImGui.GetIO().DisplaySize,
|
||||
};
|
||||
}
|
||||
|
||||
public void ToggleVisibility()
|
||||
=> _visible = !_visible;
|
||||
public override void Draw()
|
||||
{
|
||||
using var tabBar = ImRaii.TabBar("##Tabs");
|
||||
if (!tabBar)
|
||||
return;
|
||||
|
||||
UpdateState();
|
||||
|
||||
_actorTab.Draw();
|
||||
DrawSettingsTab();
|
||||
// DrawSaves();
|
||||
// DrawFixedDesignsTab();
|
||||
// DrawRevertablesTab();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_legacyTattooIcon?.Dispose();
|
||||
Dalamud.PluginInterface.UiBuilder.Draw -= Draw;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= ToggleVisibility;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= Toggle;
|
||||
}
|
||||
|
||||
private void Draw()
|
||||
{
|
||||
if (!_visible)
|
||||
return;
|
||||
|
||||
ImGui.SetNextWindowSizeConstraints(Vector2.One * MinWindowWidth * ImGui.GetIO().FontGlobalScale,
|
||||
Vector2.One * 5000 * ImGui.GetIO().FontGlobalScale);
|
||||
if (!ImGui.Begin(_glamourerHeader, ref _visible))
|
||||
{
|
||||
ImGui.End();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var tabBar = ImRaii.TabBar("##tabBar");
|
||||
if (!tabBar)
|
||||
return;
|
||||
|
||||
_inGPose = Dalamud.Objects[GPoseObjectId] != null;
|
||||
_iconSize = Vector2.One * ImGui.GetTextLineHeightWithSpacing() * 2;
|
||||
_actualIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
|
||||
_comboSelectorSize = 4 * _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||
_percentageSize = _comboSelectorSize;
|
||||
_inputIntSize = 2 * _actualIconSize.X + ImGui.GetStyle().ItemSpacing.X;
|
||||
_raceSelectorWidth = _inputIntSize + _percentageSize - _actualIconSize.X;
|
||||
_itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1;
|
||||
|
||||
DrawPlayerTab();
|
||||
DrawSaves();
|
||||
DrawFixedDesignsTab();
|
||||
DrawConfigTab();
|
||||
DrawRevertablesTab();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ImGui.End();
|
||||
}
|
||||
}
|
||||
private static string GetLabel()
|
||||
=> Glamourer.Version.Length == 0
|
||||
? "Glamourer###GlamourerConfigWindow"
|
||||
: $"Glamourer v{Glamourer.Version}###GlamourerConfigWindow";
|
||||
}
|
||||
|
||||
//public const float SelectorWidth = 200;
|
||||
//public const float MinWindowWidth = 675;
|
||||
//public const int GPoseObjectId = 201;
|
||||
//private const string PluginName = "Glamourer";
|
||||
//private readonly string _glamourerHeader;
|
||||
//
|
||||
//private readonly IReadOnlyDictionary<byte, Stain> _stains;
|
||||
//private readonly IReadOnlyDictionary<uint, ModelCeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeehara> _models;
|
||||
//private readonly IObjectIdentifier _identifier;
|
||||
//private readonly Dictionary<EquipSlot, (ComboWithFilter<Item>, ComboWithFilter<Stain>)> _combos;
|
||||
//private readonly ImGuiScene.TextureWrap? _legacyTattooIcon;
|
||||
//private readonly Dictionary<EquipSlot, string> _equipSlotNames;
|
||||
//private readonly DesignManager _designs;
|
||||
//private readonly Glamourer _plugin;
|
||||
//
|
||||
//private bool _visible;
|
||||
//private bool _inGPose;
|
||||
//
|
||||
//public Interface(Glamourer plugin)
|
||||
//{
|
||||
// _plugin = plugin;
|
||||
// _designs = plugin.Designs;
|
||||
// _glamourerHeader = Glamourer.Version.Length > 0
|
||||
// ? $"{PluginName} v{Glamourer.Version}###{PluginName}Main"
|
||||
// : $"{PluginName}###{PluginName}Main";
|
||||
// Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||
// Dalamud.PluginInterface.UiBuilder.Draw += Draw;
|
||||
// Dalamud.PluginInterface.UiBuilder.OpenConfigUi += ToggleVisibility;
|
||||
//
|
||||
// _equipSlotNames = GetEquipSlotNames();
|
||||
//
|
||||
// _stains = GameData.Stains(Dalamud.GameData);
|
||||
// _models = GameData.Models(Dalamud.GameData);
|
||||
// _identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
||||
//
|
||||
//
|
||||
// var stainCombo = CreateDefaultStainCombo(_stains.Values.ToArray());
|
||||
//
|
||||
// var equip = GameData.ItemsBySlot(Dalamud.GameData);
|
||||
// _combos = equip.ToDictionary(kvp => kvp.Key, kvp => CreateCombos(kvp.Key, kvp.Value, stainCombo));
|
||||
// _legacyTattooIcon = GetLegacyTattooIcon();
|
||||
//}
|
||||
//
|
||||
//public void ToggleVisibility()
|
||||
// => _visible = !_visible;
|
||||
//
|
||||
//
|
||||
//private void Draw()
|
||||
//{
|
||||
// if (!_visible)
|
||||
// return;
|
||||
//
|
||||
// ImGui.SetNextWindowSizeConstraints(Vector2.One * MinWindowWidth * ImGui.GetIO().FontGlobalScale,
|
||||
// Vector2.One * 5000 * ImGui.GetIO().FontGlobalScale);
|
||||
// if (!ImGui.Begin(_glamourerHeader, ref _visible))
|
||||
// {
|
||||
// ImGui.End();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// using var tabBar = ImRaii.TabBar("##tabBar");
|
||||
// if (!tabBar)
|
||||
// return;
|
||||
//
|
||||
// _inGPose = Dalamud.Objects[GPoseObjectId] != null;
|
||||
// _iconSize = Vector2.One * ImGui.GetTextLineHeightWithSpacing() * 2;
|
||||
// _actualIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
|
||||
// _comboSelectorSize = 4 * _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||
// _percentageSize = _comboSelectorSize;
|
||||
// _inputIntSize = 2 * _actualIconSize.X + ImGui.GetStyle().ItemSpacing.X;
|
||||
// _raceSelectorWidth = _inputIntSize + _percentageSize - _actualIconSize.X;
|
||||
// _itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1;
|
||||
//
|
||||
// DrawPlayerTab();
|
||||
// DrawSaves();
|
||||
// DrawFixedDesignsTab();
|
||||
// DrawConfigTab();
|
||||
// DrawRevertablesTab();
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// ImGui.End();
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,308 +1,291 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.FileSystem;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.PlayerWatch;
|
||||
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private readonly CharacterSave _currentSave = new();
|
||||
private string _newDesignName = string.Empty;
|
||||
private bool _keyboardFocus;
|
||||
private bool _holdShift;
|
||||
private bool _holdCtrl;
|
||||
private const string DesignNamePopupLabel = "Save Design As...";
|
||||
private const uint RedHeaderColor = 0xFF1818C0;
|
||||
private const uint GreenHeaderColor = 0xFF18C018;
|
||||
|
||||
private void DrawPlayerHeader()
|
||||
{
|
||||
var color = _player == null ? RedHeaderColor : GreenHeaderColor;
|
||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||
using var c = ImRaii.PushColor(ImGuiCol.Text, color)
|
||||
.Push(ImGuiCol.Button, buttonColor)
|
||||
.Push(ImGuiCol.ButtonHovered, buttonColor)
|
||||
.Push(ImGuiCol.ButtonActive, buttonColor);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f);
|
||||
}
|
||||
|
||||
private static void DrawCopyClipboardButton(CharacterSave save)
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString()))
|
||||
ImGui.SetClipboardText(save.ToBase64());
|
||||
ImGui.PopFont();
|
||||
ImGuiUtil.HoverTooltip("Copy customization code to clipboard.");
|
||||
}
|
||||
|
||||
private static void ConditionalApply(CharacterSave save, Character player)
|
||||
{
|
||||
if (ImGui.GetIO().KeyShift)
|
||||
save.ApplyOnlyCustomizations(player);
|
||||
else if (ImGui.GetIO().KeyCtrl)
|
||||
save.ApplyOnlyEquipment(player);
|
||||
else
|
||||
save.Apply(player);
|
||||
}
|
||||
|
||||
private static CharacterSave ConditionalCopy(CharacterSave save, bool shift, bool ctrl)
|
||||
{
|
||||
var copy = save.Copy();
|
||||
if (shift)
|
||||
{
|
||||
copy.Load(new CharacterEquipment());
|
||||
copy.SetHatState = false;
|
||||
copy.SetVisorState = false;
|
||||
copy.SetWeaponState = false;
|
||||
copy.WriteEquipment = CharacterEquipMask.None;
|
||||
}
|
||||
else if (ctrl)
|
||||
{
|
||||
copy.Load(CharacterCustomization.Default);
|
||||
copy.SetHatState = false;
|
||||
copy.SetVisorState = false;
|
||||
copy.SetWeaponState = false;
|
||||
copy.WriteCustomizations = false;
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
private bool DrawApplyClipboardButton()
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()) && _player != null;
|
||||
ImGui.PopFont();
|
||||
ImGuiUtil.HoverTooltip(
|
||||
"Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment.");
|
||||
|
||||
if (!applyButton)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var text = ImGui.GetClipboardText();
|
||||
if (!text.Any())
|
||||
return false;
|
||||
|
||||
var save = CharacterSave.FromString(text);
|
||||
ConditionalApply(save, _player!);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Information($"{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DrawSaveDesignButton()
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button(FontAwesomeIcon.Save.ToIconString()))
|
||||
OpenDesignNamePopup(DesignNameUse.SaveCurrent);
|
||||
|
||||
ImGui.PopFont();
|
||||
ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment.");
|
||||
|
||||
DrawDesignNamePopup(DesignNameUse.SaveCurrent);
|
||||
}
|
||||
|
||||
private void DrawTargetPlayerButton()
|
||||
{
|
||||
if (ImGui.Button("Target Player"))
|
||||
Dalamud.Targets.SetTarget(_player);
|
||||
}
|
||||
|
||||
private void DrawApplyToPlayerButton(CharacterSave save)
|
||||
{
|
||||
if (!ImGui.Button("Apply to Self"))
|
||||
return;
|
||||
|
||||
var player = _inGPose
|
||||
? (Character?)Dalamud.Objects[GPoseObjectId]
|
||||
: Dalamud.ClientState.LocalPlayer;
|
||||
var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null;
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
ConditionalApply(save, player);
|
||||
if (_inGPose)
|
||||
ConditionalApply(save, fallback!);
|
||||
Glamourer.Penumbra.UpdateCharacters(player, fallback);
|
||||
}
|
||||
|
||||
|
||||
private static Character? TransformToCustomizable(Character? actor)
|
||||
{
|
||||
if (actor == null)
|
||||
return null;
|
||||
|
||||
if (actor.ModelType() == 0)
|
||||
return actor;
|
||||
|
||||
actor.SetModelType(0);
|
||||
CharacterCustomization.Default.Write(actor.Address);
|
||||
return actor;
|
||||
}
|
||||
|
||||
private void DrawApplyToTargetButton(CharacterSave save)
|
||||
{
|
||||
if (!ImGui.Button("Apply to Target"))
|
||||
return;
|
||||
|
||||
var player = TransformToCustomizable(CharacterFactory.Convert(Dalamud.Targets.Target));
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
var fallBackCharacter = _gPoseActors.TryGetValue(player.Name.ToString(), out var f) ? f : null;
|
||||
ConditionalApply(save, player);
|
||||
if (fallBackCharacter != null)
|
||||
ConditionalApply(save, fallBackCharacter!);
|
||||
Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter);
|
||||
}
|
||||
|
||||
private void DrawRevertButton()
|
||||
{
|
||||
if (!ImGuiUtil.DrawDisabledButton("Revert", Vector2.Zero, string.Empty, _player == null))
|
||||
return;
|
||||
|
||||
Glamourer.RevertableDesigns.Revert(_player!);
|
||||
var fallBackCharacter = _gPoseActors.TryGetValue(_player!.Name.ToString(), out var f) ? f : null;
|
||||
if (fallBackCharacter != null)
|
||||
Glamourer.RevertableDesigns.Revert(fallBackCharacter);
|
||||
Glamourer.Penumbra.UpdateCharacters(_player, fallBackCharacter);
|
||||
}
|
||||
|
||||
private void SaveNewDesign(CharacterSave save)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName);
|
||||
if (!name.Any())
|
||||
return;
|
||||
|
||||
var newDesign = new Design(folder, name) { Data = save };
|
||||
folder.AddChild(newDesign);
|
||||
_designs.Designs[newDesign.FullName()] = save;
|
||||
_designs.SaveToFile();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Could not save new design {_newDesignName}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMonsterPanel()
|
||||
{
|
||||
if (DrawApplyClipboardButton())
|
||||
Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Convert to Character"))
|
||||
{
|
||||
TransformToCustomizable(_player);
|
||||
_currentLabel = _currentLabel.Replace("(Monster)", "(NPC)");
|
||||
Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
}
|
||||
|
||||
if (!_inGPose)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
DrawTargetPlayerButton();
|
||||
}
|
||||
|
||||
var currentModel = _player!.ModelType();
|
||||
using var combo = ImRaii.Combo("Model Id", currentModel.ToString());
|
||||
if (!combo)
|
||||
return;
|
||||
|
||||
foreach (var (id, _) in _models.Skip(1))
|
||||
{
|
||||
if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel)
|
||||
continue;
|
||||
|
||||
_player!.SetModelType((int)id);
|
||||
Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPlayerPanel()
|
||||
{
|
||||
DrawCopyClipboardButton(_currentSave);
|
||||
ImGui.SameLine();
|
||||
var changes = !_currentSave.WriteProtected && DrawApplyClipboardButton();
|
||||
ImGui.SameLine();
|
||||
DrawSaveDesignButton();
|
||||
ImGui.SameLine();
|
||||
DrawApplyToPlayerButton(_currentSave);
|
||||
if (!_inGPose)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
DrawApplyToTargetButton(_currentSave);
|
||||
if (_player != null && !_currentSave.WriteProtected)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
DrawTargetPlayerButton();
|
||||
}
|
||||
}
|
||||
|
||||
var data = _currentSave;
|
||||
if (!_currentSave.WriteProtected)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
DrawRevertButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
|
||||
data = data.Copy();
|
||||
}
|
||||
|
||||
if (DrawCustomization(ref data.Customizations) && _player != null)
|
||||
{
|
||||
Glamourer.RevertableDesigns.Add(_player);
|
||||
_currentSave.Customizations.Write(_player.Address);
|
||||
changes = true;
|
||||
}
|
||||
|
||||
changes |= DrawEquip(data.Equipment);
|
||||
changes |= DrawMiscellaneous(data, _player);
|
||||
|
||||
if (_player != null && changes)
|
||||
Glamourer.Penumbra.UpdateCharacters(_player);
|
||||
if (_currentSave.WriteProtected)
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
private void DrawActorPanel()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
DrawPlayerHeader();
|
||||
using var child = ImRaii.Child("##playerData", -Vector2.One, true);
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
if (_player == null || _player.ModelType() == 0)
|
||||
DrawPlayerPanel();
|
||||
else
|
||||
DrawMonsterPanel();
|
||||
}
|
||||
//private readonly CharacterSave _currentSave = new();
|
||||
//private string _newDesignName = string.Empty;
|
||||
//private bool _keyboardFocus;
|
||||
//private bool _holdShift;
|
||||
//private bool _holdCtrl;
|
||||
//private const string DesignNamePopupLabel = "Save Design As...";
|
||||
//private const uint RedHeaderColor = 0xFF1818C0;
|
||||
//private const uint GreenHeaderColor = 0xFF18C018;
|
||||
//
|
||||
//private void DrawPlayerHeader()
|
||||
//{
|
||||
// var color = _player == null ? RedHeaderColor : GreenHeaderColor;
|
||||
// var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||
// using var c = ImRaii.PushColor(ImGuiCol.Text, color)
|
||||
// .Push(ImGuiCol.Button, buttonColor)
|
||||
// .Push(ImGuiCol.ButtonHovered, buttonColor)
|
||||
// .Push(ImGuiCol.ButtonActive, buttonColor);
|
||||
// using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
// .Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
// ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f);
|
||||
//}
|
||||
//
|
||||
//private static void DrawCopyClipboardButton(CharacterSave save)
|
||||
//{
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString()))
|
||||
// ImGui.SetClipboardText(save.ToBase64());
|
||||
// ImGui.PopFont();
|
||||
// ImGuiUtil.HoverTooltip("Copy customization code to clipboard.");
|
||||
//}
|
||||
//
|
||||
//private static void ConditionalApply(CharacterSave save, Character player)
|
||||
//{
|
||||
// if (ImGui.GetIO().KeyShift)
|
||||
// save.ApplyOnlyCustomizations(player);
|
||||
// else if (ImGui.GetIO().KeyCtrl)
|
||||
// save.ApplyOnlyEquipment(player);
|
||||
// else
|
||||
// save.Apply(player);
|
||||
//}
|
||||
//
|
||||
//private static CharacterSave ConditionalCopy(CharacterSave save, bool shift, bool ctrl)
|
||||
//{
|
||||
// var copy = save.Copy();
|
||||
// if (shift)
|
||||
// {
|
||||
// copy.Load(new CharacterEquipment());
|
||||
// copy.SetHatState = false;
|
||||
// copy.SetVisorState = false;
|
||||
// copy.SetWeaponState = false;
|
||||
// copy.WriteEquipment = CharacterEquipMask.None;
|
||||
// }
|
||||
// else if (ctrl)
|
||||
// {
|
||||
// copy.Load(CharacterCustomization.Default);
|
||||
// copy.SetHatState = false;
|
||||
// copy.SetVisorState = false;
|
||||
// copy.SetWeaponState = false;
|
||||
// copy.WriteCustomizations = false;
|
||||
// }
|
||||
//
|
||||
// return copy;
|
||||
//}
|
||||
//
|
||||
//private bool DrawApplyClipboardButton()
|
||||
//{
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()) && _player != null;
|
||||
// ImGui.PopFont();
|
||||
// ImGuiUtil.HoverTooltip(
|
||||
// "Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment.");
|
||||
//
|
||||
// if (!applyButton)
|
||||
// return false;
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// var text = ImGui.GetClipboardText();
|
||||
// if (!text.Any())
|
||||
// return false;
|
||||
//
|
||||
// var save = CharacterSave.FromString(text);
|
||||
// ConditionalApply(save, _player!);
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Information($"{e}");
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
//}
|
||||
//
|
||||
//private void DrawSaveDesignButton()
|
||||
//{
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// if (ImGui.Button(FontAwesomeIcon.Save.ToIconString()))
|
||||
// OpenDesignNamePopup(DesignNameUse.SaveCurrent);
|
||||
//
|
||||
// ImGui.PopFont();
|
||||
// ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment.");
|
||||
//
|
||||
// DrawDesignNamePopup(DesignNameUse.SaveCurrent);
|
||||
//}
|
||||
//
|
||||
//private void DrawTargetPlayerButton()
|
||||
//{
|
||||
// if (ImGui.Button("Target Player"))
|
||||
// Dalamud.Targets.SetTarget(_player);
|
||||
//}
|
||||
//
|
||||
//private void DrawApplyToPlayerButton(CharacterSave save)
|
||||
//{
|
||||
// if (!ImGui.Button("Apply to Self"))
|
||||
// return;
|
||||
//
|
||||
// var player = _inGPose
|
||||
// ? (Character?)Dalamud.Objects[GPoseObjectId]
|
||||
// : Dalamud.ClientState.LocalPlayer;
|
||||
// var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null;
|
||||
// if (player == null)
|
||||
// return;
|
||||
//
|
||||
// ConditionalApply(save, player);
|
||||
// if (_inGPose)
|
||||
// ConditionalApply(save, fallback!);
|
||||
// Glamourer.Penumbra.UpdateCharacters(player, fallback);
|
||||
//}
|
||||
//
|
||||
//
|
||||
//private static Character? TransformToCustomizable(Character? actor)
|
||||
//{
|
||||
// if (actor == null)
|
||||
// return null;
|
||||
//
|
||||
// if (actor.ModelType() == 0)
|
||||
// return actor;
|
||||
//
|
||||
// actor.SetModelType(0);
|
||||
// CharacterCustomization.Default.Write(actor.Address);
|
||||
// return actor;
|
||||
//}
|
||||
//
|
||||
//private void DrawApplyToTargetButton(CharacterSave save)
|
||||
//{
|
||||
// if (!ImGui.Button("Apply to Target"))
|
||||
// return;
|
||||
//
|
||||
// var player = TransformToCustomizable(CharacterFactory.Convert(Dalamud.Targets.Target));
|
||||
// if (player == null)
|
||||
// return;
|
||||
//
|
||||
// var fallBackCharacter = _gPoseActors.TryGetValue(player.Name.ToString(), out var f) ? f : null;
|
||||
// ConditionalApply(save, player);
|
||||
// if (fallBackCharacter != null)
|
||||
// ConditionalApply(save, fallBackCharacter!);
|
||||
// Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter);
|
||||
//}
|
||||
//
|
||||
//private void DrawRevertButton()
|
||||
//{
|
||||
// if (!ImGuiUtil.DrawDisabledButton("Revert", Vector2.Zero, string.Empty, _player == null))
|
||||
// return;
|
||||
//
|
||||
// Glamourer.RevertableDesigns.Revert(_player!);
|
||||
// var fallBackCharacter = _gPoseActors.TryGetValue(_player!.Name.ToString(), out var f) ? f : null;
|
||||
// if (fallBackCharacter != null)
|
||||
// Glamourer.RevertableDesigns.Revert(fallBackCharacter);
|
||||
// Glamourer.Penumbra.UpdateCharacters(_player, fallBackCharacter);
|
||||
//}
|
||||
//
|
||||
//private void SaveNewDesign(CharacterSave save)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName);
|
||||
// if (!name.Any())
|
||||
// return;
|
||||
//
|
||||
// var newDesign = new Design(folder, name) { Data = save };
|
||||
// folder.AddChild(newDesign);
|
||||
// _designs.Designs[newDesign.FullName()] = save;
|
||||
// _designs.SaveToFile();
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Error($"Could not save new design {_newDesignName}:\n{e}");
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private void DrawMonsterPanel()
|
||||
//{
|
||||
// if (DrawApplyClipboardButton())
|
||||
// Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
//
|
||||
// ImGui.SameLine();
|
||||
// if (ImGui.Button("Convert to Character"))
|
||||
// {
|
||||
// TransformToCustomizable(_player);
|
||||
// _currentLabel = _currentLabel.Replace("(Monster)", "(NPC)");
|
||||
// Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
// }
|
||||
//
|
||||
// if (!_inGPose)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// DrawTargetPlayerButton();
|
||||
// }
|
||||
//
|
||||
// var currentModel = _player!.ModelType();
|
||||
// using var combo = ImRaii.Combo("Model Id", currentModel.ToString());
|
||||
// if (!combo)
|
||||
// return;
|
||||
//
|
||||
// foreach (var (id, _) in _models.Skip(1))
|
||||
// {
|
||||
// if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel)
|
||||
// continue;
|
||||
//
|
||||
// _player!.SetModelType((int)id);
|
||||
// Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private void DrawPlayerPanel()
|
||||
//{
|
||||
// DrawCopyClipboardButton(_currentSave);
|
||||
// ImGui.SameLine();
|
||||
// var changes = !_currentSave.WriteProtected && DrawApplyClipboardButton();
|
||||
// ImGui.SameLine();
|
||||
// DrawSaveDesignButton();
|
||||
// ImGui.SameLine();
|
||||
// DrawApplyToPlayerButton(_currentSave);
|
||||
// if (!_inGPose)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// DrawApplyToTargetButton(_currentSave);
|
||||
// if (_player != null && !_currentSave.WriteProtected)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// DrawTargetPlayerButton();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var data = _currentSave;
|
||||
// if (!_currentSave.WriteProtected)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// DrawRevertButton();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
|
||||
// data = data.Copy();
|
||||
// }
|
||||
//
|
||||
// if (DrawCustomization(ref data.Customizations) && _player != null)
|
||||
// {
|
||||
// Glamourer.RevertableDesigns.Add(_player);
|
||||
// _currentSave.Customizations.Write(_player.Address);
|
||||
// changes = true;
|
||||
// }
|
||||
//
|
||||
// changes |= DrawEquip(data.Equipment);
|
||||
// changes |= DrawMiscellaneous(data, _player);
|
||||
//
|
||||
// if (_player != null && changes)
|
||||
// Glamourer.Penumbra.UpdateCharacters(_player);
|
||||
// if (_currentSave.WriteProtected)
|
||||
// ImGui.PopStyleVar();
|
||||
//}
|
||||
//
|
||||
//private void DrawActorPanel()
|
||||
//{
|
||||
// using var group = ImRaii.Group();
|
||||
// DrawPlayerHeader();
|
||||
// using var child = ImRaii.Child("##playerData", -Vector2.One, true);
|
||||
// if (!child)
|
||||
// return;
|
||||
//
|
||||
// if (_player == null || _player.ModelType() == 0)
|
||||
// DrawPlayerPanel();
|
||||
// else
|
||||
// DrawMonsterPanel();
|
||||
//}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,223 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Logging;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.PlayerWatch;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
public const int CharacterScreenIndex = 240;
|
||||
public const int ExamineScreenIndex = 241;
|
||||
public const int FittingRoomIndex = 242;
|
||||
public const int DyePreviewIndex = 243;
|
||||
|
||||
private Character? _player;
|
||||
private string _currentLabel = string.Empty;
|
||||
private string _playerFilter = string.Empty;
|
||||
private string _playerFilterLower = string.Empty;
|
||||
private readonly Dictionary<string, int> _playerNames = new(100);
|
||||
private readonly Dictionary<string, Character?> _gPoseActors = new(CharacterScreenIndex - GPoseObjectId);
|
||||
|
||||
private void DrawPlayerFilter()
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
ImGui.SetNextItemWidth(SelectorWidth * ImGui.GetIO().FontGlobalScale);
|
||||
if (ImGui.InputTextWithHint("##playerFilter", "Filter Players...", ref _playerFilter, 32))
|
||||
_playerFilterLower = _playerFilter.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private void DrawGPoseSelectable(Character player)
|
||||
{
|
||||
var playerName = player.Name.ToString();
|
||||
if (!playerName.Any())
|
||||
return;
|
||||
|
||||
_gPoseActors[playerName] = null;
|
||||
|
||||
DrawSelectable(player, $"{playerName} (GPose)", true);
|
||||
}
|
||||
|
||||
private static string GetLabel(Character player, string playerName, int num)
|
||||
{
|
||||
if (player.ObjectKind == ObjectKind.Player)
|
||||
return num == 1 ? playerName : $"{playerName} #{num}";
|
||||
|
||||
if (player.ModelType() == 0)
|
||||
return num == 1 ? $"{playerName} (NPC)" : $"{playerName} #{num} (NPC)";
|
||||
|
||||
return num == 1 ? $"{playerName} (Monster)" : $"{playerName} #{num} (Monster)";
|
||||
}
|
||||
|
||||
private void DrawPlayerSelectable(Character player, int idx = 0)
|
||||
{
|
||||
var (playerName, modifiable) = idx switch
|
||||
{
|
||||
CharacterScreenIndex => ("Character Screen Actor", false),
|
||||
ExamineScreenIndex => ("Examine Screen Actor", false),
|
||||
FittingRoomIndex => ("Fitting Room Actor", false),
|
||||
DyePreviewIndex => ("Dye Preview Actor", false),
|
||||
_ => (player.Name.ToString(), true),
|
||||
};
|
||||
if (!playerName.Any())
|
||||
return;
|
||||
|
||||
if (_playerNames.TryGetValue(playerName, out var num))
|
||||
_playerNames[playerName] = ++num;
|
||||
else
|
||||
_playerNames[playerName] = num = 1;
|
||||
|
||||
if (_gPoseActors.ContainsKey(playerName))
|
||||
{
|
||||
_gPoseActors[playerName] = player;
|
||||
return;
|
||||
}
|
||||
|
||||
var label = GetLabel(player, playerName, num);
|
||||
DrawSelectable(player, label, modifiable);
|
||||
}
|
||||
|
||||
|
||||
private void DrawSelectable(Character player, string label, bool modifiable)
|
||||
{
|
||||
if (!_playerFilterLower.Any() || label.ToLowerInvariant().Contains(_playerFilterLower))
|
||||
if (ImGui.Selectable(label, _currentLabel == label))
|
||||
{
|
||||
_currentLabel = label;
|
||||
_currentSave.LoadCharacter(player);
|
||||
_player = player;
|
||||
_currentSave.WriteProtected = !modifiable;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentLabel != label)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_currentSave.LoadCharacter(player);
|
||||
_player = player;
|
||||
_currentSave.WriteProtected = !modifiable;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Could not load character {player.Name}s information:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSelectionButtons()
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
Character? select = null;
|
||||
var buttonWidth = Vector2.UnitX * SelectorWidth / 2;
|
||||
if (ImGui.Button(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth))
|
||||
select = Dalamud.ClientState.LocalPlayer;
|
||||
font.Pop();
|
||||
ImGuiUtil.HoverTooltip("Select the local player character.");
|
||||
ImGui.SameLine();
|
||||
font.Push(UiBuilder.IconFont);
|
||||
if (_inGPose)
|
||||
{
|
||||
style.Push(ImGuiStyleVar.Alpha, 0.5f);
|
||||
ImGui.Button(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth);
|
||||
style.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui.Button(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth))
|
||||
select = CharacterFactory.Convert(Dalamud.Targets.Target);
|
||||
}
|
||||
|
||||
font.Pop();
|
||||
ImGuiUtil.HoverTooltip("Select the current target, if it is in the list.");
|
||||
|
||||
if (select == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_currentSave.LoadCharacter(select);
|
||||
_player = select;
|
||||
_currentLabel = _player.Name.ToString();
|
||||
_currentSave.WriteProtected = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Could not load character {select.Name}s information:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPlayerSelector()
|
||||
{
|
||||
ImGui.BeginGroup();
|
||||
DrawPlayerFilter();
|
||||
if (!ImGui.BeginChild("##playerSelector",
|
||||
new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
||||
{
|
||||
ImGui.EndChild();
|
||||
ImGui.EndGroup();
|
||||
return;
|
||||
}
|
||||
|
||||
_playerNames.Clear();
|
||||
_gPoseActors.Clear();
|
||||
for (var i = GPoseObjectId; i < GPoseObjectId + 48; ++i)
|
||||
{
|
||||
var player = CharacterFactory.Convert(Dalamud.Objects[i]);
|
||||
if (player == null)
|
||||
break;
|
||||
|
||||
DrawGPoseSelectable(player);
|
||||
}
|
||||
|
||||
for (var i = 0; i < GPoseObjectId; ++i)
|
||||
{
|
||||
var player = CharacterFactory.Convert(Dalamud.Objects[i]);
|
||||
if (player != null)
|
||||
DrawPlayerSelectable(player);
|
||||
}
|
||||
|
||||
for (var i = CharacterScreenIndex; i < Dalamud.Objects.Length; ++i)
|
||||
{
|
||||
var player = CharacterFactory.Convert(Dalamud.Objects[i]);
|
||||
if (player != null)
|
||||
DrawPlayerSelectable(player, i);
|
||||
}
|
||||
|
||||
|
||||
using (var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
||||
{
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
DrawSelectionButtons();
|
||||
ImGui.EndGroup();
|
||||
}
|
||||
|
||||
private void DrawPlayerTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Current Players");
|
||||
_player = null;
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
DrawPlayerSelector();
|
||||
|
||||
if (_currentLabel.Length == 0)
|
||||
return;
|
||||
|
||||
ImGui.SameLine();
|
||||
DrawActorPanel();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
{
|
||||
internal partial class Interface
|
||||
{
|
||||
private static void DrawConfigCheckMark(string label, string tooltip, bool value, Action<bool> setter)
|
||||
{
|
||||
if (DrawCheckMark(label, value, setter))
|
||||
Glamourer.Config.Save();
|
||||
|
||||
ImGuiUtil.HoverTooltip(tooltip);
|
||||
}
|
||||
|
||||
private static void ChangeAndSave<T>(T value, T currentValue, Action<T> setter) where T : IEquatable<T>
|
||||
{
|
||||
if (value.Equals(currentValue))
|
||||
return;
|
||||
|
||||
setter(value);
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
private static void DrawColorPicker(string name, string tooltip, uint value, uint defaultValue, Action<uint> setter)
|
||||
{
|
||||
const ImGuiColorEditFlags flags = ImGuiColorEditFlags.AlphaPreviewHalf | ImGuiColorEditFlags.NoInputs;
|
||||
|
||||
var tmp = ImGui.ColorConvertU32ToFloat4(value);
|
||||
if (ImGui.ColorEdit4($"##{name}", ref tmp, flags))
|
||||
ChangeAndSave(ImGui.ColorConvertFloat4ToU32(tmp), value, setter);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button($"Default##{name}"))
|
||||
ChangeAndSave(defaultValue, value, setter);
|
||||
ImGuiUtil.HoverTooltip(
|
||||
$"Reset to default: #{defaultValue & 0xFF:X2}{(defaultValue >> 8) & 0xFF:X2}{(defaultValue >> 16) & 0xFF:X2}{defaultValue >> 24:X2}");
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(name);
|
||||
ImGuiUtil.HoverTooltip(tooltip);
|
||||
}
|
||||
|
||||
private static void DrawRestorePenumbraButton()
|
||||
{
|
||||
const string buttonLabel = "Re-Register Penumbra";
|
||||
if (!Glamourer.Config.AttachToPenumbra)
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f);
|
||||
ImGui.Button(buttonLabel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui.Button(buttonLabel))
|
||||
Glamourer.Penumbra.Reattach(true);
|
||||
|
||||
ImGuiUtil.HoverTooltip(
|
||||
"If Penumbra did not register the functions for some reason, pressing this button might help restore functionality.");
|
||||
}
|
||||
|
||||
private static void DrawConfigTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Config");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
var cfg = Glamourer.Config;
|
||||
ImGui.Dummy(Vector2.UnitY * ImGui.GetTextLineHeightWithSpacing() / 2);
|
||||
|
||||
DrawConfigCheckMark("Folders First", "Sort Folders before all designs instead of lexicographically.", cfg.FoldersFirst,
|
||||
v => cfg.FoldersFirst = v);
|
||||
DrawConfigCheckMark("Color Designs", "Color the names of designs in the selector using the colors from below for the given cases.",
|
||||
cfg.ColorDesigns,
|
||||
v => cfg.ColorDesigns = v);
|
||||
DrawConfigCheckMark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", cfg.ShowLocks,
|
||||
v => cfg.ShowLocks = v);
|
||||
DrawConfigCheckMark("Attach to Penumbra",
|
||||
"Allows you to right-click items in the Changed Items tab of a mod in Penumbra to apply them to your player character.",
|
||||
cfg.AttachToPenumbra,
|
||||
v =>
|
||||
{
|
||||
cfg.AttachToPenumbra = v;
|
||||
if (v)
|
||||
Glamourer.Penumbra.Reattach(true);
|
||||
else
|
||||
Glamourer.Penumbra.Unattach();
|
||||
});
|
||||
ImGui.SameLine();
|
||||
DrawRestorePenumbraButton();
|
||||
|
||||
DrawConfigCheckMark("Apply Fixed Designs",
|
||||
"Automatically apply fixed designs to characters and redraw them when anything changes.",
|
||||
cfg.ApplyFixedDesigns,
|
||||
v =>
|
||||
{
|
||||
cfg.ApplyFixedDesigns = v;
|
||||
if (v)
|
||||
Glamourer.PlayerWatcher.Enable();
|
||||
else
|
||||
Glamourer.PlayerWatcher.Disable();
|
||||
});
|
||||
|
||||
ImGui.Dummy(Vector2.UnitY * ImGui.GetTextLineHeightWithSpacing() / 2);
|
||||
|
||||
DrawColorPicker("Customization Color", "The color for designs that only apply their character customization.",
|
||||
cfg.CustomizationColor, GlamourerConfig.DefaultCustomizationColor, c => cfg.CustomizationColor = c);
|
||||
DrawColorPicker("Equipment Color", "The color for designs that only apply some or all of their equipment slots and stains.",
|
||||
cfg.EquipmentColor, GlamourerConfig.DefaultEquipmentColor, c => cfg.EquipmentColor = c);
|
||||
DrawColorPicker("State Color", "The color for designs that only apply some state modification.",
|
||||
cfg.StateColor, GlamourerConfig.DefaultStateColor, c => cfg.StateColor = c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,478 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
{
|
||||
internal partial class Interface
|
||||
{
|
||||
private static bool DrawColorPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value)
|
||||
{
|
||||
value = default;
|
||||
using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize);
|
||||
if (!popup)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
var count = set.Count(id);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var custom = set.Data(id, i);
|
||||
if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
|
||||
{
|
||||
value = custom;
|
||||
ret = true;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
if (i % 8 != 7)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private Vector2 _iconSize = Vector2.Zero;
|
||||
private Vector2 _actualIconSize = Vector2.Zero;
|
||||
private float _raceSelectorWidth;
|
||||
private float _inputIntSize;
|
||||
private float _comboSelectorSize;
|
||||
private float _percentageSize;
|
||||
private float _itemComboWidth;
|
||||
|
||||
private bool InputInt(string label, ref int value, int minValue, int maxValue)
|
||||
{
|
||||
var ret = false;
|
||||
var tmp = value + 1;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt(label, ref tmp, 1, 1, ImGuiInputTextFlags.EnterReturnsTrue) && tmp != value + 1 && tmp >= minValue && tmp <= maxValue)
|
||||
{
|
||||
value = tmp - 1;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [{minValue}, {maxValue}]");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static (int, Customization.Customization) GetCurrentCustomization(ref CharacterCustomization customization, CustomizationId id,
|
||||
CustomizationSet set)
|
||||
{
|
||||
var current = set.DataByValue(id, customization[id], out var custom);
|
||||
if (set.IsAvailable(id) && current < 0)
|
||||
{
|
||||
PluginLog.Warning($"Read invalid customization value {customization[id]} for {id}.");
|
||||
current = 0;
|
||||
custom = set.Data(id, 0);
|
||||
}
|
||||
|
||||
return (current, custom!.Value);
|
||||
}
|
||||
|
||||
private bool DrawColorPicker(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||
CustomizationSet set)
|
||||
{
|
||||
var ret = false;
|
||||
var count = set.Count(id);
|
||||
|
||||
var (current, custom) = GetCurrentCustomization(ref customization, id, set);
|
||||
|
||||
var popupName = $"Color Picker##{id}";
|
||||
if (ImGui.ColorButton($"{current + 1}##color_{id}", ImGui.ColorConvertU32ToFloat4(custom.Color), ImGuiColorEditFlags.None,
|
||||
_actualIconSize))
|
||||
ImGui.OpenPopup(popupName);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
if (InputInt($"##text_{id}", ref current, 1, count))
|
||||
{
|
||||
customization[id] = set.Data(id, current).Value;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
|
||||
ImGui.Text(label);
|
||||
ImGuiUtil.HoverTooltip(tooltip);
|
||||
}
|
||||
|
||||
if (!DrawColorPickerPopup(popupName, set, id, out var newCustom))
|
||||
return ret;
|
||||
|
||||
customization[id] = newCustom.Value;
|
||||
ret = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawListSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||
CustomizationSet set)
|
||||
{
|
||||
using var bigGroup = ImRaii.Group();
|
||||
var ret = false;
|
||||
int current = customization[id];
|
||||
var count = set.Count(id);
|
||||
|
||||
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
||||
if (ImGui.BeginCombo($"##combo_{id}", $"{set.Option(id)} #{current + 1}"))
|
||||
{
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
if (ImGui.Selectable($"{set.Option(id)} #{i + 1}##combo", i == current) && i != current)
|
||||
{
|
||||
customization[id] = (byte) i;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (InputInt($"##text_{id}", ref current, 1, count))
|
||||
{
|
||||
customization[id] = (byte) current;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(label);
|
||||
ImGuiUtil.HoverTooltip(tooltip);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private static readonly Vector4 NoColor = new(1f, 1f, 1f, 1f);
|
||||
private static readonly Vector4 RedColor = new(0.6f, 0.3f, 0.3f, 1f);
|
||||
|
||||
private bool DrawMultiSelector(ref CharacterCustomization customization, CustomizationSet set)
|
||||
{
|
||||
using var bigGroup = ImRaii.Group();
|
||||
var ret = false;
|
||||
var count = set.Count(CustomizationId.FacialFeaturesTattoos);
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
var face = customization.Face;
|
||||
if (set.Faces.Count < face)
|
||||
face = 1;
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var enabled = customization.FacialFeature(i);
|
||||
var feature = set.FacialFeature(face, i);
|
||||
var icon = i == count - 1
|
||||
? _legacyTattooIcon ?? Glamourer.Customization.GetIcon(feature.IconId)
|
||||
: Glamourer.Customization.GetIcon(feature.IconId);
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int) ImGui.GetStyle().FramePadding.X,
|
||||
Vector4.Zero,
|
||||
enabled ? NoColor : RedColor))
|
||||
{
|
||||
ret = true;
|
||||
customization.FacialFeature(i, !enabled);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
if (i % 4 != 3)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using var group = ImRaii.Group();
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetTextLineHeightWithSpacing() + 3 * ImGui.GetStyle().ItemSpacing.Y / 2);
|
||||
int value = customization[CustomizationId.FacialFeaturesTattoos];
|
||||
if (InputInt($"##{CustomizationId.FacialFeaturesTattoos}", ref value, 1, 256))
|
||||
{
|
||||
customization[CustomizationId.FacialFeaturesTattoos] = (byte) value;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(set.Option(CustomizationId.FacialFeaturesTattoos));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private bool DrawIconPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value)
|
||||
{
|
||||
value = default;
|
||||
using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize);
|
||||
if (!popup)
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
var count = set.Count(id);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var custom = set.Data(id, i);
|
||||
var icon = Glamourer.Customization.GetIcon(custom.IconId);
|
||||
ImGui.BeginGroup();
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||
{
|
||||
value = custom;
|
||||
ret = true;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
var text = custom.Value.ToString();
|
||||
var textWidth = ImGui.CalcTextSize(text).X;
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (_iconSize.X - textWidth + 2 * ImGui.GetStyle().FramePadding.X)/2);
|
||||
ImGui.Text(text);
|
||||
ImGui.EndGroup();
|
||||
if (i % 8 != 7)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawIconSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||
CustomizationSet set)
|
||||
{
|
||||
using var bigGroup = ImRaii.Group();
|
||||
var ret = false;
|
||||
var count = set.Count(id);
|
||||
|
||||
var current = set.DataByValue(id, customization[id], out var custom);
|
||||
if (current < 0)
|
||||
{
|
||||
label = $"{label} (Custom #{customization[id]})";
|
||||
current = 0;
|
||||
custom = set.Data(id, 0);
|
||||
}
|
||||
|
||||
var popupName = $"Style Picker##{id}";
|
||||
var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId);
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||
ImGui.OpenPopup(popupName);
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
ImGui.SameLine();
|
||||
using var group = ImRaii.Group();
|
||||
if (InputInt($"##text_{id}", ref current, 1, count))
|
||||
{
|
||||
customization[id] = set.Data(id, current).Value;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (DrawIconPickerPopup(popupName, set, id, out var newCustom))
|
||||
{
|
||||
customization[id] = newCustom.Value;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted($"{label} ({custom.Value.Value})");
|
||||
ImGuiUtil.HoverTooltip(tooltip);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private bool DrawPercentageSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||
CustomizationSet set)
|
||||
{
|
||||
using var bigGroup = ImRaii.Group();
|
||||
var ret = false;
|
||||
int value = customization[id];
|
||||
var count = set.Count(id);
|
||||
ImGui.SetNextItemWidth(_percentageSize * ImGui.GetIO().FontGlobalScale);
|
||||
if (ImGui.SliderInt($"##slider_{id}", ref value, 0, count - 1, "") && value != customization[id])
|
||||
{
|
||||
customization[id] = (byte) value;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
--value;
|
||||
if (InputInt($"##input_{id}", ref value, 0, count - 1))
|
||||
{
|
||||
customization[id] = (byte) (value + 1);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(label);
|
||||
ImGuiUtil.HoverTooltip(tooltip);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawRaceSelector(ref CharacterCustomization customization)
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
var ret = false;
|
||||
ImGui.SetNextItemWidth(_raceSelectorWidth);
|
||||
if (ImGui.BeginCombo("##subRaceCombo", ClanName(customization.Clan, customization.Gender)))
|
||||
{
|
||||
for (var i = 0; i < (int) SubRace.Veena; ++i)
|
||||
{
|
||||
if (ImGui.Selectable(ClanName((SubRace) i + 1, customization.Gender), (int) customization.Clan == i + 1))
|
||||
{
|
||||
var race = (SubRace) i + 1;
|
||||
ret |= ChangeRace(ref customization, race);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(
|
||||
$"{Glamourer.Customization.GetName(CustomName.Gender)} & {Glamourer.Customization.GetName(CustomName.Clan)}");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawGenderSelector(ref CharacterCustomization customization)
|
||||
{
|
||||
var ret = false;
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
var icon = customization.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
|
||||
var restricted = false;
|
||||
if (customization.Race == Race.Hrothgar)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.25f);
|
||||
icon = FontAwesomeIcon.MarsDouble;
|
||||
restricted = true;
|
||||
}
|
||||
|
||||
if (ImGui.Button(icon.ToIconString(), _actualIconSize) && !restricted)
|
||||
{
|
||||
var gender = customization.Gender == Gender.Male ? Gender.Female : Gender.Male;
|
||||
ret = ChangeGender(ref customization, gender);
|
||||
}
|
||||
|
||||
if (restricted)
|
||||
ImGui.PopStyleVar();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawPicker(CustomizationSet set, CustomizationId id, ref CharacterCustomization customization)
|
||||
{
|
||||
if (!set.IsAvailable(id))
|
||||
return false;
|
||||
|
||||
switch (set.Type(id))
|
||||
{
|
||||
case CharaMakeParams.MenuType.ColorPicker: return DrawColorPicker(set.OptionName[(int) id], "", ref customization, id, set);
|
||||
case CharaMakeParams.MenuType.ListSelector: return DrawListSelector(set.OptionName[(int) id], "", ref customization, id, set);
|
||||
case CharaMakeParams.MenuType.IconSelector: return DrawIconSelector(set.OptionName[(int) id], "", ref customization, id, set);
|
||||
case CharaMakeParams.MenuType.MultiIconSelector: return DrawMultiSelector(ref customization, set);
|
||||
case CharaMakeParams.MenuType.Percentage:
|
||||
return DrawPercentageSelector(set.OptionName[(int) id], "", ref customization, id, set);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static CustomizationId[] GetCustomizationOrder()
|
||||
{
|
||||
var ret = (CustomizationId[])Enum.GetValues(typeof(CustomizationId));
|
||||
ret[(int) CustomizationId.TattooColor] = CustomizationId.EyeColorL;
|
||||
ret[(int) CustomizationId.EyeColorL] = CustomizationId.EyeColorR;
|
||||
ret[(int) CustomizationId.EyeColorR] = CustomizationId.TattooColor;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static readonly CustomizationId[] AllCustomizations = GetCustomizationOrder();
|
||||
|
||||
private bool DrawCustomization(ref CharacterCustomization custom)
|
||||
{
|
||||
if (!ImGui.CollapsingHeader("Character Customization"))
|
||||
return false;
|
||||
|
||||
var ret = DrawGenderSelector(ref custom);
|
||||
ImGui.SameLine();
|
||||
ret |= DrawRaceSelector(ref custom);
|
||||
|
||||
var set = Glamourer.Customization.GetList(custom.Clan, custom.Gender);
|
||||
|
||||
foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.Percentage))
|
||||
ret |= DrawPicker(set, id, ref custom);
|
||||
|
||||
var odd = true;
|
||||
foreach (var id in AllCustomizations.Where((c, _) => set.Type(c) == CharaMakeParams.MenuType.IconSelector))
|
||||
{
|
||||
ret |= DrawPicker(set, id, ref custom);
|
||||
if (odd)
|
||||
ImGui.SameLine();
|
||||
odd = !odd;
|
||||
}
|
||||
|
||||
if (!odd)
|
||||
ImGui.NewLine();
|
||||
|
||||
ret |= DrawPicker(set, CustomizationId.FacialFeaturesTattoos, ref custom);
|
||||
|
||||
foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.ListSelector))
|
||||
ret |= DrawPicker(set, id, ref custom);
|
||||
|
||||
odd = true;
|
||||
foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.ColorPicker))
|
||||
{
|
||||
ret |= DrawPicker(set, id, ref custom);
|
||||
if (odd)
|
||||
ImGui.SameLine();
|
||||
odd = !odd;
|
||||
}
|
||||
|
||||
if (!odd)
|
||||
ImGui.NewLine();
|
||||
|
||||
var tmp = custom.HighlightsOn;
|
||||
if (ImGui.Checkbox(set.Option(CustomizationId.HighlightsOnFlag), ref tmp) && tmp != custom.HighlightsOn)
|
||||
{
|
||||
custom.HighlightsOn = tmp;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
var xPos = _inputIntSize + _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||
ImGui.SameLine(xPos);
|
||||
tmp = custom.FacePaintReversed;
|
||||
if (ImGui.Checkbox($"{Glamourer.Customization.GetName(CustomName.Reverse)} {set.Option(CustomizationId.FacePaint)}", ref tmp)
|
||||
&& tmp != custom.FacePaintReversed)
|
||||
{
|
||||
custom.FacePaintReversed = tmp;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
tmp = custom.SmallIris;
|
||||
if (ImGui.Checkbox($"{Glamourer.Customization.GetName(CustomName.IrisSmall)} {Glamourer.Customization.GetName(CustomName.IrisSize)}",
|
||||
ref tmp)
|
||||
&& tmp != custom.SmallIris)
|
||||
{
|
||||
custom.SmallIris = tmp;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (custom.Race != Race.Hrothgar)
|
||||
{
|
||||
tmp = custom.Lipstick;
|
||||
ImGui.SameLine(xPos);
|
||||
if (ImGui.Checkbox(set.Option(CustomizationId.LipColor), ref tmp) && tmp != custom.Lipstick)
|
||||
{
|
||||
custom.Lipstick = tmp;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,371 +4,371 @@ using System.Numerics;
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.FileSystem;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private int _totalObject;
|
||||
|
||||
private bool _inDesignMode;
|
||||
private Design? _selection;
|
||||
private string _newChildName = string.Empty;
|
||||
|
||||
private void DrawDesignSelector()
|
||||
{
|
||||
_totalObject = 0;
|
||||
ImGui.BeginGroup();
|
||||
if (ImGui.BeginChild("##selector", new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
||||
{
|
||||
DrawFolderContent(_designs.FileSystem.Root, Glamourer.Config.FoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
ImGui.EndChild();
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
DrawDesignSelectorButtons();
|
||||
ImGui.EndGroup();
|
||||
}
|
||||
|
||||
private void DrawPasteClipboardButton()
|
||||
{
|
||||
if (_selection!.Data.WriteProtected)
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString());
|
||||
ImGui.PopFont();
|
||||
if (_selection!.Data.WriteProtected)
|
||||
ImGui.PopStyleVar();
|
||||
|
||||
ImGuiUtil.HoverTooltip("Overwrite with customization code from clipboard.");
|
||||
|
||||
if (_selection!.Data.WriteProtected || !applyButton)
|
||||
return;
|
||||
|
||||
var text = ImGui.GetClipboardText();
|
||||
if (!text.Any())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_selection!.Data = CharacterSave.FromString(text);
|
||||
_designs.SaveToFile();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Information($"{e}");
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawNewFolderButton()
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
||||
OpenDesignNamePopup(DesignNameUse.NewFolder);
|
||||
ImGui.PopFont();
|
||||
ImGuiUtil.HoverTooltip("Create a new, empty Folder.");
|
||||
|
||||
DrawDesignNamePopup(DesignNameUse.NewFolder);
|
||||
}
|
||||
|
||||
private void DrawNewDesignButton()
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
||||
OpenDesignNamePopup(DesignNameUse.NewDesign);
|
||||
ImGui.PopFont();
|
||||
ImGuiUtil.HoverTooltip("Create a new, empty Design.");
|
||||
|
||||
DrawDesignNamePopup(DesignNameUse.NewDesign);
|
||||
}
|
||||
|
||||
private void DrawClipboardDesignButton()
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button(FontAwesomeIcon.Paste.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
||||
OpenDesignNamePopup(DesignNameUse.FromClipboard);
|
||||
ImGui.PopFont();
|
||||
ImGuiUtil.HoverTooltip("Create a new design from the customization string in your clipboard.");
|
||||
|
||||
DrawDesignNamePopup(DesignNameUse.FromClipboard);
|
||||
}
|
||||
|
||||
private void DrawDeleteDesignButton()
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
var style = _selection == null;
|
||||
if (style)
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
||||
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
|
||||
{
|
||||
_designs.DeleteAllChildren(_selection, false);
|
||||
_selection = null;
|
||||
}
|
||||
|
||||
ImGui.PopFont();
|
||||
if (style)
|
||||
ImGui.PopStyleVar();
|
||||
ImGuiUtil.HoverTooltip("Delete the currently selected Design.");
|
||||
}
|
||||
|
||||
private void DrawDuplicateDesignButton()
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (_selection == null)
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
||||
if (ImGui.Button(FontAwesomeIcon.Clone.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
|
||||
OpenDesignNamePopup(DesignNameUse.DuplicateDesign);
|
||||
ImGui.PopFont();
|
||||
if (_selection == null)
|
||||
ImGui.PopStyleVar();
|
||||
ImGuiUtil.HoverTooltip(
|
||||
"Clone the currently selected Design.\nHold Shift to only clone the customizations.\nHold Control to only clone the equipment.");
|
||||
|
||||
DrawDesignNamePopup(DesignNameUse.DuplicateDesign);
|
||||
}
|
||||
|
||||
private void DrawDesignSelectorButtons()
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0f);
|
||||
|
||||
DrawNewFolderButton();
|
||||
ImGui.SameLine();
|
||||
DrawNewDesignButton();
|
||||
ImGui.SameLine();
|
||||
DrawClipboardDesignButton();
|
||||
ImGui.SameLine();
|
||||
DrawDuplicateDesignButton();
|
||||
ImGui.SameLine();
|
||||
DrawDeleteDesignButton();
|
||||
}
|
||||
|
||||
private void DrawDesignHeaderButtons()
|
||||
{
|
||||
DrawCopyClipboardButton(_selection!.Data);
|
||||
ImGui.SameLine();
|
||||
DrawPasteClipboardButton();
|
||||
ImGui.SameLine();
|
||||
DrawApplyToPlayerButton(_selection!.Data);
|
||||
if (!_inGPose)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
DrawApplyToTargetButton(_selection!.Data);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
DrawCheckbox("Write Protected", _selection!.Data.WriteProtected, v => _selection!.Data.WriteProtected = v, false);
|
||||
}
|
||||
|
||||
private void DrawDesignPanel()
|
||||
{
|
||||
if (ImGui.BeginChild("##details", -Vector2.One * 0.001f, true))
|
||||
{
|
||||
DrawDesignHeaderButtons();
|
||||
var data = _selection!.Data;
|
||||
var prot = _selection!.Data.WriteProtected;
|
||||
if (prot)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
|
||||
data = data.Copy();
|
||||
}
|
||||
|
||||
DrawGeneralSettings(data, prot);
|
||||
var mask = data.WriteEquipment;
|
||||
if (DrawEquip(data.Equipment, ref mask) && !prot)
|
||||
{
|
||||
data.WriteEquipment = mask;
|
||||
_designs.SaveToFile();
|
||||
}
|
||||
|
||||
if (DrawCustomization(ref data.Customizations) && !prot)
|
||||
_designs.SaveToFile();
|
||||
|
||||
if (DrawMiscellaneous(data, null) && !prot)
|
||||
_designs.SaveToFile();
|
||||
|
||||
if (prot)
|
||||
ImGui.PopStyleVar();
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSaves()
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.IndentSpacing, 12.5f * ImGui.GetIO().FontGlobalScale);
|
||||
using var tab = ImRaii.TabItem("Designs");
|
||||
_inDesignMode = tab.Success;
|
||||
if (!_inDesignMode)
|
||||
return;
|
||||
|
||||
DrawDesignSelector();
|
||||
|
||||
if (_selection != null)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
DrawDesignPanel();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCheckbox(string label, bool value, Action<bool> setter, bool prot)
|
||||
{
|
||||
var tmp = value;
|
||||
if (ImGui.Checkbox(label, ref tmp) && tmp != value)
|
||||
{
|
||||
setter(tmp);
|
||||
if (!prot)
|
||||
_designs.SaveToFile();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGeneralSettings(CharacterSave data, bool prot)
|
||||
{
|
||||
ImGui.BeginGroup();
|
||||
DrawCheckbox("Apply Customizations", data.WriteCustomizations, v => data.WriteCustomizations = v, prot);
|
||||
DrawCheckbox("Write Weapon State", data.SetWeaponState, v => data.SetWeaponState = v, prot);
|
||||
ImGui.EndGroup();
|
||||
ImGui.SameLine();
|
||||
ImGui.BeginGroup();
|
||||
DrawCheckbox("Write Hat State", data.SetHatState, v => data.SetHatState = v, prot);
|
||||
DrawCheckbox("Write Visor State", data.SetVisorState, v => data.SetVisorState = v, prot);
|
||||
ImGui.EndGroup();
|
||||
}
|
||||
|
||||
private void RenameChildInput(IFileSystemBase child)
|
||||
{
|
||||
ImGui.SetNextItemWidth(150);
|
||||
if (!ImGui.InputTextWithHint("##fsNewName", "Rename...", ref _newChildName, 64,
|
||||
ImGuiInputTextFlags.EnterReturnsTrue))
|
||||
return;
|
||||
|
||||
if (_newChildName.Any() && _newChildName != child.Name)
|
||||
try
|
||||
{
|
||||
var oldPath = child.FullName();
|
||||
if (_designs.FileSystem.Rename(child, _newChildName))
|
||||
_designs.UpdateAllChildren(oldPath, child);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Could not rename {child.Name} to {_newChildName}:\n{e}");
|
||||
}
|
||||
else if (child is Folder f)
|
||||
try
|
||||
{
|
||||
var oldPath = child.FullName();
|
||||
if (_designs.FileSystem.Merge(f, f.Parent, true))
|
||||
_designs.UpdateAllChildren(oldPath, f.Parent);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Could not merge folder {child.Name} into parent:\n{e}");
|
||||
}
|
||||
|
||||
_newChildName = string.Empty;
|
||||
}
|
||||
|
||||
private void ContextMenu(IFileSystemBase child)
|
||||
{
|
||||
var label = $"##fsPopup{child.FullName()}";
|
||||
if (ImGui.BeginPopup(label))
|
||||
{
|
||||
if (ImGui.MenuItem("Delete") && ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift)
|
||||
_designs.DeleteAllChildren(child, false);
|
||||
ImGuiUtil.HoverTooltip("Hold Control and Shift to delete.");
|
||||
|
||||
RenameChildInput(child);
|
||||
|
||||
if (child is Design d && ImGui.MenuItem("Copy to Clipboard"))
|
||||
ImGui.SetClipboardText(d.Data.ToBase64());
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
{
|
||||
_newChildName = child.Name;
|
||||
ImGui.OpenPopup(label);
|
||||
}
|
||||
}
|
||||
|
||||
private static uint GetDesignColor(CharacterSave save)
|
||||
{
|
||||
const uint white = 0xFFFFFFFF;
|
||||
const uint grey = 0xFF808080;
|
||||
if (!Glamourer.Config.ColorDesigns)
|
||||
return white;
|
||||
|
||||
var changesStates = save.SetHatState || save.SetVisorState || save.SetWeaponState || save.IsWet || save.Alpha != 1.0f;
|
||||
if (save.WriteCustomizations)
|
||||
if (save.WriteEquipment != CharacterEquipMask.None)
|
||||
return white;
|
||||
else
|
||||
return changesStates ? white : Glamourer.Config.CustomizationColor;
|
||||
|
||||
if (save.WriteEquipment != CharacterEquipMask.None)
|
||||
return changesStates ? white : Glamourer.Config.EquipmentColor;
|
||||
|
||||
return changesStates ? Glamourer.Config.StateColor : grey;
|
||||
}
|
||||
|
||||
private void DrawFolderContent(Folder folder, SortMode mode)
|
||||
{
|
||||
foreach (var child in folder.AllChildren(mode).ToArray())
|
||||
{
|
||||
if (child.IsFolder(out var subFolder))
|
||||
{
|
||||
var treeNode = ImGui.TreeNodeEx($"{subFolder.Name}##{_totalObject}");
|
||||
DrawOrnaments(child);
|
||||
|
||||
if (treeNode)
|
||||
{
|
||||
DrawFolderContent(subFolder, mode);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
else
|
||||
{
|
||||
_totalObject += subFolder.TotalDescendantLeaves();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (child is not Design d)
|
||||
continue;
|
||||
|
||||
++_totalObject;
|
||||
var color = GetDesignColor(d.Data);
|
||||
using var c = ImRaii.PushColor(ImGuiCol.Text, color);
|
||||
|
||||
var selected = ImGui.Selectable($"{child.Name}##{_totalObject}", ReferenceEquals(child, _selection));
|
||||
c.Pop();
|
||||
DrawOrnaments(child);
|
||||
|
||||
if (Glamourer.Config.ShowLocks && d.Data.WriteProtected)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
c.Push(ImGuiCol.Text, color);
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.Lock.ToIconString());
|
||||
}
|
||||
|
||||
if (selected)
|
||||
_selection = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawOrnaments(IFileSystemBase child)
|
||||
{
|
||||
FileSystemImGui.DragDropSource(child);
|
||||
if (FileSystemImGui.DragDropTarget(_designs.FileSystem, child, out var oldPath, out var draggedFolder))
|
||||
_designs.UpdateAllChildren(oldPath, draggedFolder!);
|
||||
ContextMenu(child);
|
||||
}
|
||||
}
|
||||
//internal partial class Interface
|
||||
//{
|
||||
// private int _totalObject;
|
||||
//
|
||||
// private bool _inDesignMode;
|
||||
// private Design? _selection;
|
||||
// private string _newChildName = string.Empty;
|
||||
//
|
||||
// private void DrawDesignSelector()
|
||||
// {
|
||||
// _totalObject = 0;
|
||||
// ImGui.BeginGroup();
|
||||
// if (ImGui.BeginChild("##selector", new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
||||
// {
|
||||
// DrawFolderContent(_designs.FileSystem.Root, Glamourer.Config.FoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical);
|
||||
// ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
// ImGui.EndChild();
|
||||
// ImGui.PopStyleVar();
|
||||
// }
|
||||
//
|
||||
// DrawDesignSelectorButtons();
|
||||
// ImGui.EndGroup();
|
||||
// }
|
||||
//
|
||||
// private void DrawPasteClipboardButton()
|
||||
// {
|
||||
// if (_selection!.Data.WriteProtected)
|
||||
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
||||
//
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString());
|
||||
// ImGui.PopFont();
|
||||
// if (_selection!.Data.WriteProtected)
|
||||
// ImGui.PopStyleVar();
|
||||
//
|
||||
// ImGuiUtil.HoverTooltip("Overwrite with customization code from clipboard.");
|
||||
//
|
||||
// if (_selection!.Data.WriteProtected || !applyButton)
|
||||
// return;
|
||||
//
|
||||
// var text = ImGui.GetClipboardText();
|
||||
// if (!text.Any())
|
||||
// return;
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// _selection!.Data = CharacterSave.FromString(text);
|
||||
// _designs.SaveToFile();
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Information($"{e}");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void DrawNewFolderButton()
|
||||
// {
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
||||
// OpenDesignNamePopup(DesignNameUse.NewFolder);
|
||||
// ImGui.PopFont();
|
||||
// ImGuiUtil.HoverTooltip("Create a new, empty Folder.");
|
||||
//
|
||||
// DrawDesignNamePopup(DesignNameUse.NewFolder);
|
||||
// }
|
||||
//
|
||||
// private void DrawNewDesignButton()
|
||||
// {
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
||||
// OpenDesignNamePopup(DesignNameUse.NewDesign);
|
||||
// ImGui.PopFont();
|
||||
// ImGuiUtil.HoverTooltip("Create a new, empty Design.");
|
||||
//
|
||||
// DrawDesignNamePopup(DesignNameUse.NewDesign);
|
||||
// }
|
||||
//
|
||||
// private void DrawClipboardDesignButton()
|
||||
// {
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// if (ImGui.Button(FontAwesomeIcon.Paste.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
||||
// OpenDesignNamePopup(DesignNameUse.FromClipboard);
|
||||
// ImGui.PopFont();
|
||||
// ImGuiUtil.HoverTooltip("Create a new design from the customization string in your clipboard.");
|
||||
//
|
||||
// DrawDesignNamePopup(DesignNameUse.FromClipboard);
|
||||
// }
|
||||
//
|
||||
// private void DrawDeleteDesignButton()
|
||||
// {
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// var style = _selection == null;
|
||||
// if (style)
|
||||
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
||||
// if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
|
||||
// {
|
||||
// _designs.DeleteAllChildren(_selection, false);
|
||||
// _selection = null;
|
||||
// }
|
||||
//
|
||||
// ImGui.PopFont();
|
||||
// if (style)
|
||||
// ImGui.PopStyleVar();
|
||||
// ImGuiUtil.HoverTooltip("Delete the currently selected Design.");
|
||||
// }
|
||||
//
|
||||
// private void DrawDuplicateDesignButton()
|
||||
// {
|
||||
// ImGui.PushFont(UiBuilder.IconFont);
|
||||
// if (_selection == null)
|
||||
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
||||
// if (ImGui.Button(FontAwesomeIcon.Clone.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
|
||||
// OpenDesignNamePopup(DesignNameUse.DuplicateDesign);
|
||||
// ImGui.PopFont();
|
||||
// if (_selection == null)
|
||||
// ImGui.PopStyleVar();
|
||||
// ImGuiUtil.HoverTooltip(
|
||||
// "Clone the currently selected Design.\nHold Shift to only clone the customizations.\nHold Control to only clone the equipment.");
|
||||
//
|
||||
// DrawDesignNamePopup(DesignNameUse.DuplicateDesign);
|
||||
// }
|
||||
//
|
||||
// private void DrawDesignSelectorButtons()
|
||||
// {
|
||||
// using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
// .Push(ImGuiStyleVar.FrameRounding, 0f);
|
||||
//
|
||||
// DrawNewFolderButton();
|
||||
// ImGui.SameLine();
|
||||
// DrawNewDesignButton();
|
||||
// ImGui.SameLine();
|
||||
// DrawClipboardDesignButton();
|
||||
// ImGui.SameLine();
|
||||
// DrawDuplicateDesignButton();
|
||||
// ImGui.SameLine();
|
||||
// DrawDeleteDesignButton();
|
||||
// }
|
||||
//
|
||||
// private void DrawDesignHeaderButtons()
|
||||
// {
|
||||
// DrawCopyClipboardButton(_selection!.Data);
|
||||
// ImGui.SameLine();
|
||||
// DrawPasteClipboardButton();
|
||||
// ImGui.SameLine();
|
||||
// DrawApplyToPlayerButton(_selection!.Data);
|
||||
// if (!_inGPose)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// DrawApplyToTargetButton(_selection!.Data);
|
||||
// }
|
||||
//
|
||||
// ImGui.SameLine();
|
||||
// DrawCheckbox("Write Protected", _selection!.Data.WriteProtected, v => _selection!.Data.WriteProtected = v, false);
|
||||
// }
|
||||
//
|
||||
// private void DrawDesignPanel()
|
||||
// {
|
||||
// if (ImGui.BeginChild("##details", -Vector2.One * 0.001f, true))
|
||||
// {
|
||||
// DrawDesignHeaderButtons();
|
||||
// var data = _selection!.Data;
|
||||
// var prot = _selection!.Data.WriteProtected;
|
||||
// if (prot)
|
||||
// {
|
||||
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
|
||||
// data = data.Copy();
|
||||
// }
|
||||
//
|
||||
// DrawGeneralSettings(data, prot);
|
||||
// var mask = data.WriteEquipment;
|
||||
// if (DrawEquip(data.Equipment, ref mask) && !prot)
|
||||
// {
|
||||
// data.WriteEquipment = mask;
|
||||
// _designs.SaveToFile();
|
||||
// }
|
||||
//
|
||||
// if (DrawCustomization(ref data.Customizations) && !prot)
|
||||
// _designs.SaveToFile();
|
||||
//
|
||||
// if (DrawMiscellaneous(data, null) && !prot)
|
||||
// _designs.SaveToFile();
|
||||
//
|
||||
// if (prot)
|
||||
// ImGui.PopStyleVar();
|
||||
//
|
||||
// ImGui.EndChild();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void DrawSaves()
|
||||
// {
|
||||
// using var style = ImRaii.PushStyle(ImGuiStyleVar.IndentSpacing, 12.5f * ImGui.GetIO().FontGlobalScale);
|
||||
// using var tab = ImRaii.TabItem("Designs");
|
||||
// _inDesignMode = tab.Success;
|
||||
// if (!_inDesignMode)
|
||||
// return;
|
||||
//
|
||||
// DrawDesignSelector();
|
||||
//
|
||||
// if (_selection != null)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// DrawDesignPanel();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void DrawCheckbox(string label, bool value, Action<bool> setter, bool prot)
|
||||
// {
|
||||
// var tmp = value;
|
||||
// if (ImGui.Checkbox(label, ref tmp) && tmp != value)
|
||||
// {
|
||||
// setter(tmp);
|
||||
// if (!prot)
|
||||
// _designs.SaveToFile();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void DrawGeneralSettings(CharacterSave data, bool prot)
|
||||
// {
|
||||
// ImGui.BeginGroup();
|
||||
// DrawCheckbox("Apply Customizations", data.WriteCustomizations, v => data.WriteCustomizations = v, prot);
|
||||
// DrawCheckbox("Write Weapon State", data.SetWeaponState, v => data.SetWeaponState = v, prot);
|
||||
// ImGui.EndGroup();
|
||||
// ImGui.SameLine();
|
||||
// ImGui.BeginGroup();
|
||||
// DrawCheckbox("Write Hat State", data.SetHatState, v => data.SetHatState = v, prot);
|
||||
// DrawCheckbox("Write Visor State", data.SetVisorState, v => data.SetVisorState = v, prot);
|
||||
// ImGui.EndGroup();
|
||||
// }
|
||||
//
|
||||
// private void RenameChildInput(IFileSystemBase child)
|
||||
// {
|
||||
// ImGui.SetNextItemWidth(150);
|
||||
// if (!ImGui.InputTextWithHint("##fsNewName", "Rename...", ref _newChildName, 64,
|
||||
// ImGuiInputTextFlags.EnterReturnsTrue))
|
||||
// return;
|
||||
//
|
||||
// if (_newChildName.Any() && _newChildName != child.Name)
|
||||
// try
|
||||
// {
|
||||
// var oldPath = child.FullName();
|
||||
// if (_designs.FileSystem.Rename(child, _newChildName))
|
||||
// _designs.UpdateAllChildren(oldPath, child);
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Error($"Could not rename {child.Name} to {_newChildName}:\n{e}");
|
||||
// }
|
||||
// else if (child is Folder f)
|
||||
// try
|
||||
// {
|
||||
// var oldPath = child.FullName();
|
||||
// if (_designs.FileSystem.Merge(f, f.Parent, true))
|
||||
// _designs.UpdateAllChildren(oldPath, f.Parent);
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Error($"Could not merge folder {child.Name} into parent:\n{e}");
|
||||
// }
|
||||
//
|
||||
// _newChildName = string.Empty;
|
||||
// }
|
||||
//
|
||||
// private void ContextMenu(IFileSystemBase child)
|
||||
// {
|
||||
// var label = $"##fsPopup{child.FullName()}";
|
||||
// if (ImGui.BeginPopup(label))
|
||||
// {
|
||||
// if (ImGui.MenuItem("Delete") && ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift)
|
||||
// _designs.DeleteAllChildren(child, false);
|
||||
// ImGuiUtil.HoverTooltip("Hold Control and Shift to delete.");
|
||||
//
|
||||
// RenameChildInput(child);
|
||||
//
|
||||
// if (child is Design d && ImGui.MenuItem("Copy to Clipboard"))
|
||||
// ImGui.SetClipboardText(d.Data.ToBase64());
|
||||
//
|
||||
// ImGui.EndPopup();
|
||||
// }
|
||||
//
|
||||
// if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
// {
|
||||
// _newChildName = child.Name;
|
||||
// ImGui.OpenPopup(label);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private static uint GetDesignColor(CharacterSave save)
|
||||
// {
|
||||
// const uint white = 0xFFFFFFFF;
|
||||
// const uint grey = 0xFF808080;
|
||||
// if (!Glamourer.Config.ColorDesigns)
|
||||
// return white;
|
||||
//
|
||||
// var changesStates = save.SetHatState || save.SetVisorState || save.SetWeaponState || save.IsWet || save.Alpha != 1.0f;
|
||||
// if (save.WriteCustomizations)
|
||||
// if (save.WriteEquipment != CharacterEquipMask.None)
|
||||
// return white;
|
||||
// else
|
||||
// return changesStates ? white : Glamourer.Config.CustomizationColor;
|
||||
//
|
||||
// if (save.WriteEquipment != CharacterEquipMask.None)
|
||||
// return changesStates ? white : Glamourer.Config.EquipmentColor;
|
||||
//
|
||||
// return changesStates ? Glamourer.Config.StateColor : grey;
|
||||
// }
|
||||
//
|
||||
// private void DrawFolderContent(Folder folder, SortMode mode)
|
||||
// {
|
||||
// foreach (var child in folder.AllChildren(mode).ToArray())
|
||||
// {
|
||||
// if (child.IsFolder(out var subFolder))
|
||||
// {
|
||||
// var treeNode = ImGui.TreeNodeEx($"{subFolder.Name}##{_totalObject}");
|
||||
// DrawOrnaments(child);
|
||||
//
|
||||
// if (treeNode)
|
||||
// {
|
||||
// DrawFolderContent(subFolder, mode);
|
||||
// ImGui.TreePop();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _totalObject += subFolder.TotalDescendantLeaves();
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (child is not Design d)
|
||||
// continue;
|
||||
//
|
||||
// ++_totalObject;
|
||||
// var color = GetDesignColor(d.Data);
|
||||
// using var c = ImRaii.PushColor(ImGuiCol.Text, color);
|
||||
//
|
||||
// var selected = ImGui.Selectable($"{child.Name}##{_totalObject}", ReferenceEquals(child, _selection));
|
||||
// c.Pop();
|
||||
// DrawOrnaments(child);
|
||||
//
|
||||
// if (Glamourer.Config.ShowLocks && d.Data.WriteProtected)
|
||||
// {
|
||||
// ImGui.SameLine();
|
||||
// using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
// c.Push(ImGuiCol.Text, color);
|
||||
// ImGui.TextUnformatted(FontAwesomeIcon.Lock.ToIconString());
|
||||
// }
|
||||
//
|
||||
// if (selected)
|
||||
// _selection = d;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void DrawOrnaments(IFileSystemBase child)
|
||||
// {
|
||||
// FileSystemImGui.DragDropSource(child);
|
||||
// if (FileSystemImGui.DragDropTarget(_designs.FileSystem, child, out var oldPath, out var draggedFolder))
|
||||
// _designs.UpdateAllChildren(oldPath, draggedFolder!);
|
||||
// ContextMenu(child);
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,184 +1,196 @@
|
|||
using Dalamud.Interface;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using Lumina.Text;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
{
|
||||
internal partial class Interface
|
||||
{
|
||||
private bool DrawStainSelector(ComboWithFilter<Stain> stainCombo, EquipSlot slot, StainId stainIdx)
|
||||
{
|
||||
stainCombo.PostPreview = null;
|
||||
if (_stains.TryGetValue((byte) stainIdx, out var stain))
|
||||
{
|
||||
var previewPush = PushColor(stain, ImGuiCol.FrameBg);
|
||||
stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush);
|
||||
}
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
var change = stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx);
|
||||
if (!change && (byte) stainIdx != 0)
|
||||
{
|
||||
ImGuiUtil.HoverTooltip("Right-click to clear.");
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
{
|
||||
change = true;
|
||||
newStain = Stain.None;
|
||||
}
|
||||
}
|
||||
|
||||
if (!change)
|
||||
return false;
|
||||
|
||||
if (_player == null)
|
||||
return _inDesignMode && (_selection?.Data.WriteStain(slot, newStain.RowIndex) ?? false);
|
||||
|
||||
Glamourer.RevertableDesigns.Add(_player);
|
||||
newStain.Write(_player.Address, slot);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private bool DrawItemSelector(ComboWithFilter<Item> equipCombo, Lumina.Excel.GeneratedSheets.Item item, EquipSlot slot = EquipSlot.Unknown)
|
||||
{
|
||||
var currentName = item.Name.ToString();
|
||||
var change = equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && newItem.Base.RowId != item.RowId;
|
||||
if (!change && !ReferenceEquals(item, SmallClothes))
|
||||
{
|
||||
ImGuiUtil.HoverTooltip("Right-click to clear.");
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
{
|
||||
change = true;
|
||||
newItem = Item.Nothing(slot);
|
||||
}
|
||||
}
|
||||
|
||||
if (!change)
|
||||
return false;
|
||||
|
||||
newItem = new Item(newItem.Base, newItem.Name, slot);
|
||||
if (_player == null)
|
||||
return _inDesignMode && (_selection?.Data.WriteItem(newItem) ?? false);
|
||||
|
||||
Glamourer.RevertableDesigns.Add(_player);
|
||||
newItem.Write(_player.Address);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private static bool DrawCheckbox(CharacterEquipMask flag, ref CharacterEquipMask mask)
|
||||
{
|
||||
var tmp = (uint) mask;
|
||||
var ret = false;
|
||||
if (ImGui.CheckboxFlags($"##flag_{(uint) flag}", ref tmp, (uint) flag) && tmp != (uint) mask)
|
||||
{
|
||||
mask = (CharacterEquipMask) tmp;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Enable writing this slot in this save.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothes = new(){ Name = new SeString("Nothing"), RowId = 0 };
|
||||
private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothesNpc = new(){ Name = new SeString("Smallclothes (NPC)"), RowId = 1 };
|
||||
private static readonly Lumina.Excel.GeneratedSheets.Item Unknown = new(){ Name = new SeString("Unknown"), RowId = 2 };
|
||||
|
||||
private Lumina.Excel.GeneratedSheets.Item Identify(SetId set, WeaponType weapon, ushort variant, EquipSlot slot)
|
||||
{
|
||||
return (uint) set switch
|
||||
{
|
||||
0 => SmallClothes,
|
||||
9903 => SmallClothesNpc,
|
||||
_ => _identifier.Identify(set, weapon, variant, slot) ?? Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
private bool DrawEquipSlot(EquipSlot slot, CharacterArmor equip)
|
||||
{
|
||||
var (equipCombo, stainCombo) = _combos[slot];
|
||||
|
||||
var ret = DrawStainSelector(stainCombo, slot, equip.Stain);
|
||||
ImGui.SameLine();
|
||||
var item = Identify(equip.Set, new WeaponType(), equip.Variant, slot);
|
||||
ret |= DrawItemSelector(equipCombo, item, slot);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawEquipSlotWithCheck(EquipSlot slot, CharacterArmor equip, CharacterEquipMask flag, ref CharacterEquipMask mask)
|
||||
{
|
||||
var ret = DrawCheckbox(flag, ref mask);
|
||||
ImGui.SameLine();
|
||||
ret |= DrawEquipSlot(slot, equip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawWeapon(EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
var (equipCombo, stainCombo) = _combos[slot];
|
||||
|
||||
var ret = DrawStainSelector(stainCombo, slot, weapon.Stain);
|
||||
ImGui.SameLine();
|
||||
var item = Identify(weapon.Set, weapon.Type, weapon.Variant, slot);
|
||||
ret |= DrawItemSelector(equipCombo, item, slot);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawWeaponWithCheck(EquipSlot slot, CharacterWeapon weapon, CharacterEquipMask flag, ref CharacterEquipMask mask)
|
||||
{
|
||||
var ret = DrawCheckbox(flag, ref mask);
|
||||
ImGui.SameLine();
|
||||
ret |= DrawWeapon(slot, weapon);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawEquip(CharacterEquipment equip)
|
||||
{
|
||||
var ret = false;
|
||||
if (ImGui.CollapsingHeader("Character Equipment"))
|
||||
{
|
||||
ret |= DrawWeapon(EquipSlot.MainHand, equip.MainHand);
|
||||
ret |= DrawWeapon(EquipSlot.OffHand, equip.OffHand);
|
||||
ret |= DrawEquipSlot(EquipSlot.Head, equip.Head);
|
||||
ret |= DrawEquipSlot(EquipSlot.Body, equip.Body);
|
||||
ret |= DrawEquipSlot(EquipSlot.Hands, equip.Hands);
|
||||
ret |= DrawEquipSlot(EquipSlot.Legs, equip.Legs);
|
||||
ret |= DrawEquipSlot(EquipSlot.Feet, equip.Feet);
|
||||
ret |= DrawEquipSlot(EquipSlot.Ears, equip.Ears);
|
||||
ret |= DrawEquipSlot(EquipSlot.Neck, equip.Neck);
|
||||
ret |= DrawEquipSlot(EquipSlot.Wrists, equip.Wrists);
|
||||
ret |= DrawEquipSlot(EquipSlot.RFinger, equip.RFinger);
|
||||
ret |= DrawEquipSlot(EquipSlot.LFinger, equip.LFinger);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawEquip(CharacterEquipment equip, ref CharacterEquipMask mask)
|
||||
{
|
||||
var ret = false;
|
||||
if (ImGui.CollapsingHeader("Character Equipment"))
|
||||
{
|
||||
ret |= DrawWeaponWithCheck(EquipSlot.MainHand, equip.MainHand, CharacterEquipMask.MainHand, ref mask);
|
||||
ret |= DrawWeaponWithCheck(EquipSlot.OffHand, equip.OffHand, CharacterEquipMask.OffHand, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Head, equip.Head, CharacterEquipMask.Head, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Body, equip.Body, CharacterEquipMask.Body, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Hands, equip.Hands, CharacterEquipMask.Hands, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Legs, equip.Legs, CharacterEquipMask.Legs, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Feet, equip.Feet, CharacterEquipMask.Feet, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Ears, equip.Ears, CharacterEquipMask.Ears, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Neck, equip.Neck, CharacterEquipMask.Neck, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Wrists, equip.Wrists, CharacterEquipMask.Wrists, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.RFinger, equip.RFinger, CharacterEquipMask.RFinger, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.LFinger, equip.LFinger, CharacterEquipMask.LFinger, ref mask);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
//internal partial class Interface
|
||||
//{
|
||||
// private bool DrawStainSelector(ComboWithFilter<Stain> stainCombo, EquipSlot slot, StainId stainIdx)
|
||||
// {
|
||||
// stainCombo.PostPreview = null;
|
||||
// if (_stains.TryGetValue((byte)stainIdx, out var stain))
|
||||
// {
|
||||
// var previewPush = PushColor(stain, ImGuiCol.FrameBg);
|
||||
// stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush);
|
||||
// }
|
||||
//
|
||||
// var change = stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx);
|
||||
// if (!change && (byte)stainIdx != 0)
|
||||
// {
|
||||
// ImGuiUtil.HoverTooltip("Right-click to clear.");
|
||||
// if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
// {
|
||||
// change = true;
|
||||
// newStain = Stain.None;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (!change)
|
||||
// return false;
|
||||
//
|
||||
// if (_player == null)
|
||||
// return _inDesignMode && (_selection?.Data.WriteStain(slot, newStain.RowIndex) ?? false);
|
||||
//
|
||||
// Glamourer.RevertableDesigns.Add(_player);
|
||||
// newStain.Write(_player.Address, slot);
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// private bool DrawItemSelector(ComboWithFilter<Item> equipCombo, Lumina.Excel.GeneratedSheets.Item item, EquipSlot slot = EquipSlot.Unknown)
|
||||
// {
|
||||
// var currentName = item.Name.ToString();
|
||||
// var change = equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && newItem.Base.RowId != item.RowId;
|
||||
// if (!change && !ReferenceEquals(item, SmallClothes))
|
||||
// {
|
||||
// ImGuiUtil.HoverTooltip("Right-click to clear.");
|
||||
// if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
// {
|
||||
// change = true;
|
||||
// newItem = Item.Nothing(slot);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (!change)
|
||||
// return false;
|
||||
//
|
||||
// newItem = new Item(newItem.Base, newItem.Name, slot);
|
||||
// if (_player == null)
|
||||
// return _inDesignMode && (_selection?.Data.WriteItem(newItem) ?? false);
|
||||
//
|
||||
// Glamourer.RevertableDesigns.Add(_player);
|
||||
// newItem.Write(_player.Address);
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// private static bool DrawCheckbox(CharacterEquipMask flag, ref CharacterEquipMask mask)
|
||||
// {
|
||||
// var tmp = (uint)mask;
|
||||
// var ret = false;
|
||||
// if (ImGui.CheckboxFlags($"##flag_{(uint)flag}", ref tmp, (uint)flag) && tmp != (uint)mask)
|
||||
// {
|
||||
// mask = (CharacterEquipMask)tmp;
|
||||
// ret = true;
|
||||
// }
|
||||
//
|
||||
// if (ImGui.IsItemHovered())
|
||||
// ImGui.SetTooltip("Enable writing this slot in this save.");
|
||||
// return ret;
|
||||
// }
|
||||
//
|
||||
// private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothes = new()
|
||||
// {
|
||||
// Name = new SeString("Nothing"),
|
||||
// RowId = 0,
|
||||
// };
|
||||
//
|
||||
// private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothesNpc = new()
|
||||
// {
|
||||
// Name = new SeString("Smallclothes (NPC)"),
|
||||
// RowId = 1,
|
||||
// };
|
||||
//
|
||||
// private static readonly Lumina.Excel.GeneratedSheets.Item Unknown = new()
|
||||
// {
|
||||
// Name = new SeString("Unknown"),
|
||||
// RowId = 2,
|
||||
// };
|
||||
//
|
||||
// private Lumina.Excel.GeneratedSheets.Item Identify(SetId set, WeaponType weapon, ushort variant, EquipSlot slot)
|
||||
// {
|
||||
// return (uint)set switch
|
||||
// {
|
||||
// 0 => SmallClothes,
|
||||
// 9903 => SmallClothesNpc,
|
||||
// _ => _identifier.Identify(set, weapon, variant, slot) ?? Unknown,
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// private bool DrawEquipSlot(EquipSlot slot, CharacterArmor equip)
|
||||
// {
|
||||
// var (equipCombo, stainCombo) = _combos[slot];
|
||||
//
|
||||
// var ret = DrawStainSelector(stainCombo, slot, equip.Stain);
|
||||
// ImGui.SameLine();
|
||||
// var item = Identify(equip.Set, new WeaponType(), equip.Variant, slot);
|
||||
// ret |= DrawItemSelector(equipCombo, item, slot);
|
||||
//
|
||||
// return ret;
|
||||
// }
|
||||
//
|
||||
// private bool DrawEquipSlotWithCheck(EquipSlot slot, CharacterArmor equip, CharacterEquipMask flag, ref CharacterEquipMask mask)
|
||||
// {
|
||||
// var ret = DrawCheckbox(flag, ref mask);
|
||||
// ImGui.SameLine();
|
||||
// ret |= DrawEquipSlot(slot, equip);
|
||||
// return ret;
|
||||
// }
|
||||
//
|
||||
// private bool DrawWeapon(EquipSlot slot, CharacterWeapon weapon)
|
||||
// {
|
||||
// var (equipCombo, stainCombo) = _combos[slot];
|
||||
//
|
||||
// var ret = DrawStainSelector(stainCombo, slot, weapon.Stain);
|
||||
// ImGui.SameLine();
|
||||
// var item = Identify(weapon.Set, weapon.Type, weapon.Variant, slot);
|
||||
// ret |= DrawItemSelector(equipCombo, item, slot);
|
||||
//
|
||||
// return ret;
|
||||
// }
|
||||
//
|
||||
// private bool DrawWeaponWithCheck(EquipSlot slot, CharacterWeapon weapon, CharacterEquipMask flag, ref CharacterEquipMask mask)
|
||||
// {
|
||||
// var ret = DrawCheckbox(flag, ref mask);
|
||||
// ImGui.SameLine();
|
||||
// ret |= DrawWeapon(slot, weapon);
|
||||
// return ret;
|
||||
// }
|
||||
//
|
||||
// private bool DrawEquip(CharacterEquipment equip)
|
||||
// {
|
||||
// var ret = false;
|
||||
// if (ImGui.CollapsingHeader("Character Equipment"))
|
||||
// {
|
||||
// ret |= DrawWeapon(EquipSlot.MainHand, equip.MainHand);
|
||||
// ret |= DrawWeapon(EquipSlot.OffHand, equip.OffHand);
|
||||
// ret |= DrawEquipSlot(EquipSlot.Head, equip.Head);
|
||||
// ret |= DrawEquipSlot(EquipSlot.Body, equip.Body);
|
||||
// ret |= DrawEquipSlot(EquipSlot.Hands, equip.Hands);
|
||||
// ret |= DrawEquipSlot(EquipSlot.Legs, equip.Legs);
|
||||
// ret |= DrawEquipSlot(EquipSlot.Feet, equip.Feet);
|
||||
// ret |= DrawEquipSlot(EquipSlot.Ears, equip.Ears);
|
||||
// ret |= DrawEquipSlot(EquipSlot.Neck, equip.Neck);
|
||||
// ret |= DrawEquipSlot(EquipSlot.Wrists, equip.Wrists);
|
||||
// ret |= DrawEquipSlot(EquipSlot.RFinger, equip.RFinger);
|
||||
// ret |= DrawEquipSlot(EquipSlot.LFinger, equip.LFinger);
|
||||
// }
|
||||
//
|
||||
// return ret;
|
||||
// }
|
||||
//
|
||||
// private bool DrawEquip(CharacterEquipment equip, ref CharacterEquipMask mask)
|
||||
// {
|
||||
// var ret = false;
|
||||
// if (ImGui.CollapsingHeader("Character Equipment"))
|
||||
// {
|
||||
// ret |= DrawWeaponWithCheck(EquipSlot.MainHand, equip.MainHand, CharacterEquipMask.MainHand, ref mask);
|
||||
// ret |= DrawWeaponWithCheck(EquipSlot.OffHand, equip.OffHand, CharacterEquipMask.OffHand, ref mask);
|
||||
// ret |= DrawEquipSlotWithCheck(EquipSlot.Head, equip.Head, CharacterEquipMask.Head, ref mask);
|
||||
// ret |= DrawEquipSlotWithCheck(EquipSlot.Body, equip.Body, CharacterEquipMask.Body, ref mask);
|
||||
// ret |= DrawEquipSlotWithCheck(EquipSlot.Hands, equip.Hands, CharacterEquipMask.Hands, ref mask);
|
||||
// ret |= DrawEquipSlotWithCheck(EquipSlot.Legs, equip.Legs, CharacterEquipMask.Legs, ref mask);
|
||||
// ret |= DrawEquipSlotWithCheck(EquipSlot.Feet, equip.Feet, CharacterEquipMask.Feet, ref mask);
|
||||
// ret |= DrawEquipSlotWithCheck(EquipSlot.Ears, equip.Ears, CharacterEquipMask.Ears, ref mask);
|
||||
// ret |= DrawEquipSlotWithCheck(EquipSlot.Neck, equip.Neck, CharacterEquipMask.Neck, ref mask);
|
||||
// ret |= DrawEquipSlotWithCheck(EquipSlot.Wrists, equip.Wrists, CharacterEquipMask.Wrists, ref mask);
|
||||
// ret |= DrawEquipSlotWithCheck(EquipSlot.RFinger, equip.RFinger, CharacterEquipMask.RFinger, ref mask);
|
||||
// ret |= DrawEquipSlotWithCheck(EquipSlot.LFinger, equip.LFinger, CharacterEquipMask.LFinger, ref mask);
|
||||
// }
|
||||
//
|
||||
// return ret;
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -4,165 +4,165 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.FileSystem;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private const string FixDragDropLabel = "##FixDragDrop";
|
||||
|
||||
private List<string>? _fullPathCache;
|
||||
private string _newFixCharacterName = string.Empty;
|
||||
private string _newFixDesignPath = string.Empty;
|
||||
private JobGroup? _newFixDesignGroup;
|
||||
private Design? _newFixDesign;
|
||||
private int _fixDragDropIdx = -1;
|
||||
|
||||
private static unsafe bool IsDropping()
|
||||
=> ImGui.AcceptDragDropPayload(FixDragDropLabel).NativePtr != null;
|
||||
|
||||
private void DrawFixedDesignsTab()
|
||||
{
|
||||
_newFixDesignGroup ??= Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
|
||||
|
||||
using var tabItem = ImRaii.TabItem("Fixed Designs");
|
||||
if (!tabItem)
|
||||
{
|
||||
_fullPathCache = null;
|
||||
_newFixDesign = null;
|
||||
_newFixDesignPath = string.Empty;
|
||||
_newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
|
||||
return;
|
||||
}
|
||||
|
||||
_fullPathCache ??= Glamourer.FixedDesignManager.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList();
|
||||
|
||||
using var table = ImRaii.Table("##FixedTable", 4);
|
||||
var buttonWidth = 23.5f * ImGuiHelpers.GlobalScale;
|
||||
|
||||
ImGui.TableSetupColumn("##DeleteColumn", ImGuiTableColumnFlags.WidthFixed, 2 * buttonWidth);
|
||||
ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableSetupColumn("Jobs", ImGuiTableColumnFlags.WidthFixed, 175 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableHeadersRow();
|
||||
var xPos = 0f;
|
||||
|
||||
using var style = new ImRaii.Style();
|
||||
using var font = new ImRaii.Font();
|
||||
for (var i = 0; i < _fullPathCache.Count; ++i)
|
||||
{
|
||||
var path = _fullPathCache[i];
|
||||
var name = Glamourer.FixedDesignManager.FixedDesigns.Data[i];
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
style.Push(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2);
|
||||
font.Push(UiBuilder.IconFont);
|
||||
if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconChar()}##{i}"))
|
||||
{
|
||||
_fullPathCache.RemoveAt(i--);
|
||||
Glamourer.FixedDesignManager.FixedDesigns.Remove(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
var tmp = name.Enabled;
|
||||
ImGui.SameLine();
|
||||
xPos = ImGui.GetCursorPosX();
|
||||
if (ImGui.Checkbox($"##Enabled{i}", ref tmp))
|
||||
if (tmp && Glamourer.FixedDesignManager.FixedDesigns.EnableDesign(name)
|
||||
|| !tmp && Glamourer.FixedDesignManager.FixedDesigns.DisableDesign(name))
|
||||
{
|
||||
Glamourer.Config.FixedDesigns[i].Enabled = tmp;
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
style.Pop();
|
||||
font.Pop();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Selectable($"{name.Name}##Fix{i}");
|
||||
if (ImGui.BeginDragDropSource())
|
||||
{
|
||||
_fixDragDropIdx = i;
|
||||
ImGui.SetDragDropPayload("##FixDragDrop", IntPtr.Zero, 0);
|
||||
ImGui.Text($"Dragging {name.Name} ({path})...");
|
||||
ImGui.EndDragDropSource();
|
||||
}
|
||||
|
||||
if (ImGui.BeginDragDropTarget())
|
||||
{
|
||||
if (IsDropping() && _fixDragDropIdx >= 0)
|
||||
{
|
||||
var d = Glamourer.FixedDesignManager.FixedDesigns.Data[_fixDragDropIdx];
|
||||
Glamourer.FixedDesignManager.FixedDesigns.Move(d, i);
|
||||
var p = _fullPathCache[_fixDragDropIdx];
|
||||
_fullPathCache.RemoveAt(_fixDragDropIdx);
|
||||
_fullPathCache.Insert(i, p);
|
||||
_fixDragDropIdx = -1;
|
||||
}
|
||||
|
||||
ImGui.EndDragDropTarget();
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(Glamourer.FixedDesignManager.FixedDesigns.Data[i].Jobs.Name);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text(path);
|
||||
}
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
font.Push(UiBuilder.IconFont);
|
||||
|
||||
ImGui.SetCursorPosX(xPos);
|
||||
if (_newFixDesign == null || _newFixCharacterName == string.Empty)
|
||||
{
|
||||
style.Push(ImGuiStyleVar.Alpha, 0.5f);
|
||||
ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix");
|
||||
style.Pop();
|
||||
}
|
||||
else if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix"))
|
||||
{
|
||||
_fullPathCache.Add(_newFixDesignPath);
|
||||
Glamourer.FixedDesignManager.FixedDesigns.Add(_newFixCharacterName, _newFixDesign, _newFixDesignGroup.Value, false);
|
||||
_newFixCharacterName = string.Empty;
|
||||
_newFixDesignPath = string.Empty;
|
||||
_newFixDesign = null;
|
||||
_newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
|
||||
}
|
||||
|
||||
font.Pop();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.InputTextWithHint("##NewFix", "Enter new Character", ref _newFixCharacterName, 32);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
using var combo = ImRaii.Combo("##NewFixDesignGroup", _newFixDesignGroup.Value.Name);
|
||||
if (combo)
|
||||
foreach (var (id, group) in Glamourer.FixedDesignManager.FixedDesigns.JobGroups)
|
||||
{
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
if (ImGui.Selectable($"{group.Name}##NewFixDesignGroup", group.Name == _newFixDesignGroup.Value.Name))
|
||||
_newFixDesignGroup = group;
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
using var combo2 = ImRaii.Combo("##NewFixPath", _newFixDesignPath);
|
||||
if (!combo2)
|
||||
return;
|
||||
|
||||
foreach (var design in _plugin.Designs.FileSystem.Root.AllLeaves(SortMode.Lexicographical).Cast<Design>())
|
||||
{
|
||||
var fullName = design.FullName();
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
if (!ImGui.Selectable($"{fullName}##NewFixDesign", fullName == _newFixDesignPath))
|
||||
continue;
|
||||
|
||||
_newFixDesignPath = fullName;
|
||||
_newFixDesign = design;
|
||||
}
|
||||
}
|
||||
}
|
||||
//internal partial class Interface
|
||||
//{
|
||||
// private const string FixDragDropLabel = "##FixDragDrop";
|
||||
//
|
||||
// private List<string>? _fullPathCache;
|
||||
// private string _newFixCharacterName = string.Empty;
|
||||
// private string _newFixDesignPath = string.Empty;
|
||||
// private JobGroup? _newFixDesignGroup;
|
||||
// private Design? _newFixDesign;
|
||||
// private int _fixDragDropIdx = -1;
|
||||
//
|
||||
// private static unsafe bool IsDropping()
|
||||
// => ImGui.AcceptDragDropPayload(FixDragDropLabel).NativePtr != null;
|
||||
//
|
||||
// private void DrawFixedDesignsTab()
|
||||
// {
|
||||
// _newFixDesignGroup ??= Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
|
||||
//
|
||||
// using var tabItem = ImRaii.TabItem("Fixed Designs");
|
||||
// if (!tabItem)
|
||||
// {
|
||||
// _fullPathCache = null;
|
||||
// _newFixDesign = null;
|
||||
// _newFixDesignPath = string.Empty;
|
||||
// _newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// _fullPathCache ??= Glamourer.FixedDesignManager.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList();
|
||||
//
|
||||
// using var table = ImRaii.Table("##FixedTable", 4);
|
||||
// var buttonWidth = 23.5f * ImGuiHelpers.GlobalScale;
|
||||
//
|
||||
// ImGui.TableSetupColumn("##DeleteColumn", ImGuiTableColumnFlags.WidthFixed, 2 * buttonWidth);
|
||||
// ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale);
|
||||
// ImGui.TableSetupColumn("Jobs", ImGuiTableColumnFlags.WidthFixed, 175 * ImGuiHelpers.GlobalScale);
|
||||
// ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthStretch);
|
||||
// ImGui.TableHeadersRow();
|
||||
// var xPos = 0f;
|
||||
//
|
||||
// using var style = new ImRaii.Style();
|
||||
// using var font = new ImRaii.Font();
|
||||
// for (var i = 0; i < _fullPathCache.Count; ++i)
|
||||
// {
|
||||
// var path = _fullPathCache[i];
|
||||
// var name = Glamourer.FixedDesignManager.FixedDesigns.Data[i];
|
||||
//
|
||||
// ImGui.TableNextRow();
|
||||
// ImGui.TableNextColumn();
|
||||
// style.Push(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2);
|
||||
// font.Push(UiBuilder.IconFont);
|
||||
// if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconChar()}##{i}"))
|
||||
// {
|
||||
// _fullPathCache.RemoveAt(i--);
|
||||
// Glamourer.FixedDesignManager.FixedDesigns.Remove(name);
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// var tmp = name.Enabled;
|
||||
// ImGui.SameLine();
|
||||
// xPos = ImGui.GetCursorPosX();
|
||||
// if (ImGui.Checkbox($"##Enabled{i}", ref tmp))
|
||||
// if (tmp && Glamourer.FixedDesignManager.FixedDesigns.EnableDesign(name)
|
||||
// || !tmp && Glamourer.FixedDesignManager.FixedDesigns.DisableDesign(name))
|
||||
// {
|
||||
// Glamourer.Config.FixedDesigns[i].Enabled = tmp;
|
||||
// Glamourer.Config.Save();
|
||||
// }
|
||||
//
|
||||
// style.Pop();
|
||||
// font.Pop();
|
||||
// ImGui.TableNextColumn();
|
||||
// ImGui.Selectable($"{name.Name}##Fix{i}");
|
||||
// if (ImGui.BeginDragDropSource())
|
||||
// {
|
||||
// _fixDragDropIdx = i;
|
||||
// ImGui.SetDragDropPayload("##FixDragDrop", IntPtr.Zero, 0);
|
||||
// ImGui.Text($"Dragging {name.Name} ({path})...");
|
||||
// ImGui.EndDragDropSource();
|
||||
// }
|
||||
//
|
||||
// if (ImGui.BeginDragDropTarget())
|
||||
// {
|
||||
// if (IsDropping() && _fixDragDropIdx >= 0)
|
||||
// {
|
||||
// var d = Glamourer.FixedDesignManager.FixedDesigns.Data[_fixDragDropIdx];
|
||||
// Glamourer.FixedDesignManager.FixedDesigns.Move(d, i);
|
||||
// var p = _fullPathCache[_fixDragDropIdx];
|
||||
// _fullPathCache.RemoveAt(_fixDragDropIdx);
|
||||
// _fullPathCache.Insert(i, p);
|
||||
// _fixDragDropIdx = -1;
|
||||
// }
|
||||
//
|
||||
// ImGui.EndDragDropTarget();
|
||||
// }
|
||||
//
|
||||
// ImGui.TableNextColumn();
|
||||
// ImGui.Text(Glamourer.FixedDesignManager.FixedDesigns.Data[i].Jobs.Name);
|
||||
// ImGui.TableNextColumn();
|
||||
// ImGui.Text(path);
|
||||
// }
|
||||
//
|
||||
// ImGui.TableNextRow();
|
||||
// ImGui.TableNextColumn();
|
||||
// font.Push(UiBuilder.IconFont);
|
||||
//
|
||||
// ImGui.SetCursorPosX(xPos);
|
||||
// if (_newFixDesign == null || _newFixCharacterName == string.Empty)
|
||||
// {
|
||||
// style.Push(ImGuiStyleVar.Alpha, 0.5f);
|
||||
// ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix");
|
||||
// style.Pop();
|
||||
// }
|
||||
// else if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix"))
|
||||
// {
|
||||
// _fullPathCache.Add(_newFixDesignPath);
|
||||
// Glamourer.FixedDesignManager.FixedDesigns.Add(_newFixCharacterName, _newFixDesign, _newFixDesignGroup.Value, false);
|
||||
// _newFixCharacterName = string.Empty;
|
||||
// _newFixDesignPath = string.Empty;
|
||||
// _newFixDesign = null;
|
||||
// _newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
|
||||
// }
|
||||
//
|
||||
// font.Pop();
|
||||
// ImGui.TableNextColumn();
|
||||
// ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||
// ImGui.InputTextWithHint("##NewFix", "Enter new Character", ref _newFixCharacterName, 32);
|
||||
// ImGui.TableNextColumn();
|
||||
// ImGui.SetNextItemWidth(-1);
|
||||
// using var combo = ImRaii.Combo("##NewFixDesignGroup", _newFixDesignGroup.Value.Name);
|
||||
// if (combo)
|
||||
// foreach (var (id, group) in Glamourer.FixedDesignManager.FixedDesigns.JobGroups)
|
||||
// {
|
||||
// ImGui.SetNextItemWidth(-1);
|
||||
// if (ImGui.Selectable($"{group.Name}##NewFixDesignGroup", group.Name == _newFixDesignGroup.Value.Name))
|
||||
// _newFixDesignGroup = group;
|
||||
// }
|
||||
//
|
||||
// ImGui.TableNextColumn();
|
||||
// ImGui.SetNextItemWidth(-1);
|
||||
// using var combo2 = ImRaii.Combo("##NewFixPath", _newFixDesignPath);
|
||||
// if (!combo2)
|
||||
// return;
|
||||
//
|
||||
// foreach (var design in _plugin.Designs.FileSystem.Root.AllLeaves(SortMode.Lexicographical).Cast<Design>())
|
||||
// {
|
||||
// var fullName = design.FullName();
|
||||
// ImGui.SetNextItemWidth(-1);
|
||||
// if (!ImGui.Selectable($"{fullName}##NewFixDesign", fullName == _newFixDesignPath))
|
||||
// continue;
|
||||
//
|
||||
// _newFixDesignPath = fullName;
|
||||
// _newFixDesign = design;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -6,197 +6,98 @@ using Glamourer.Structs;
|
|||
using ImGuiNET;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
{
|
||||
internal partial class Interface
|
||||
{
|
||||
// Push the stain color to type and if it is too bright, turn the text color black.
|
||||
// Return number of pushed styles.
|
||||
private static int PushColor(Stain stain, ImGuiCol type = ImGuiCol.Button)
|
||||
{
|
||||
ImGui.PushStyleColor(type, stain.RgbaColor);
|
||||
if (stain.Intensity > 127)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, 0xFF101010);
|
||||
return 2;
|
||||
}
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
return 1;
|
||||
}
|
||||
//internal partial class Interface
|
||||
//{
|
||||
// // Push the stain color to type and if it is too bright, turn the text color black.
|
||||
// // Return number of pushed styles.
|
||||
// private static int PushColor(Stain stain, ImGuiCol type = ImGuiCol.Button)
|
||||
// {
|
||||
// ImGui.PushStyleColor(type, stain.RgbaColor);
|
||||
// if (stain.Intensity > 127)
|
||||
// {
|
||||
// ImGui.PushStyleColor(ImGuiCol.Text, 0xFF101010);
|
||||
// return 2;
|
||||
// }
|
||||
//
|
||||
// return 1;
|
||||
// }
|
||||
//
|
||||
|
||||
// Go through a whole customization struct and fix up all settings that need fixing.
|
||||
private static void FixUpAttributes(ref CharacterCustomization customization)
|
||||
{
|
||||
var set = Glamourer.Customization.GetList(customization.Clan, customization.Gender);
|
||||
foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId)))
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case CustomizationId.Race: break;
|
||||
case CustomizationId.Clan: break;
|
||||
case CustomizationId.BodyType: break;
|
||||
case CustomizationId.Gender: break;
|
||||
case CustomizationId.FacialFeaturesTattoos: break;
|
||||
case CustomizationId.HighlightsOnFlag: break;
|
||||
case CustomizationId.Face: break;
|
||||
default:
|
||||
var count = set.Count(id);
|
||||
if (set.DataByValue(id, customization[id], out _) < 0)
|
||||
if (count == 0)
|
||||
customization[id] = 0;
|
||||
else
|
||||
customization[id] = set.Data(id, 0).Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
// Change a race and fix up all required customizations afterwards.
|
||||
private static bool ChangeRace(ref CharacterCustomization customization, SubRace clan)
|
||||
{
|
||||
if (clan == customization.Clan)
|
||||
return false;
|
||||
|
||||
var race = clan.ToRace();
|
||||
customization.Race = race;
|
||||
customization.Clan = clan;
|
||||
|
||||
if (race == Race.Hrothgar)
|
||||
customization.Gender = Gender.Male;
|
||||
|
||||
FixUpAttributes(ref customization);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change a gender and fix up all required customizations afterwards.
|
||||
private static bool ChangeGender(ref CharacterCustomization customization, Gender gender)
|
||||
{
|
||||
if (gender == customization.Gender)
|
||||
return false;
|
||||
|
||||
customization.Gender = gender;
|
||||
FixUpAttributes(ref customization);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string ClanName(SubRace race, Gender gender)
|
||||
{
|
||||
if (gender == Gender.Female)
|
||||
return race switch
|
||||
{
|
||||
SubRace.Midlander => Glamourer.Customization.GetName(CustomName.MidlanderM),
|
||||
SubRace.Highlander => Glamourer.Customization.GetName(CustomName.HighlanderM),
|
||||
SubRace.Wildwood => Glamourer.Customization.GetName(CustomName.WildwoodM),
|
||||
SubRace.Duskwight => Glamourer.Customization.GetName(CustomName.DuskwightM),
|
||||
SubRace.Plainsfolk => Glamourer.Customization.GetName(CustomName.PlainsfolkM),
|
||||
SubRace.Dunesfolk => Glamourer.Customization.GetName(CustomName.DunesfolkM),
|
||||
SubRace.SeekerOfTheSun => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunM),
|
||||
SubRace.KeeperOfTheMoon => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonM),
|
||||
SubRace.Seawolf => Glamourer.Customization.GetName(CustomName.SeawolfM),
|
||||
SubRace.Hellsguard => Glamourer.Customization.GetName(CustomName.HellsguardM),
|
||||
SubRace.Raen => Glamourer.Customization.GetName(CustomName.RaenM),
|
||||
SubRace.Xaela => Glamourer.Customization.GetName(CustomName.XaelaM),
|
||||
SubRace.Helion => Glamourer.Customization.GetName(CustomName.HelionM),
|
||||
SubRace.Lost => Glamourer.Customization.GetName(CustomName.LostM),
|
||||
SubRace.Rava => Glamourer.Customization.GetName(CustomName.RavaF),
|
||||
SubRace.Veena => Glamourer.Customization.GetName(CustomName.VeenaF),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
|
||||
};
|
||||
|
||||
return race switch
|
||||
{
|
||||
SubRace.Midlander => Glamourer.Customization.GetName(CustomName.MidlanderF),
|
||||
SubRace.Highlander => Glamourer.Customization.GetName(CustomName.HighlanderF),
|
||||
SubRace.Wildwood => Glamourer.Customization.GetName(CustomName.WildwoodF),
|
||||
SubRace.Duskwight => Glamourer.Customization.GetName(CustomName.DuskwightF),
|
||||
SubRace.Plainsfolk => Glamourer.Customization.GetName(CustomName.PlainsfolkF),
|
||||
SubRace.Dunesfolk => Glamourer.Customization.GetName(CustomName.DunesfolkF),
|
||||
SubRace.SeekerOfTheSun => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunF),
|
||||
SubRace.KeeperOfTheMoon => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonF),
|
||||
SubRace.Seawolf => Glamourer.Customization.GetName(CustomName.SeawolfF),
|
||||
SubRace.Hellsguard => Glamourer.Customization.GetName(CustomName.HellsguardF),
|
||||
SubRace.Raen => Glamourer.Customization.GetName(CustomName.RaenF),
|
||||
SubRace.Xaela => Glamourer.Customization.GetName(CustomName.XaelaF),
|
||||
SubRace.Helion => Glamourer.Customization.GetName(CustomName.HelionM),
|
||||
SubRace.Lost => Glamourer.Customization.GetName(CustomName.LostM),
|
||||
SubRace.Rava => Glamourer.Customization.GetName(CustomName.RavaF),
|
||||
SubRace.Veena => Glamourer.Customization.GetName(CustomName.VeenaF),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
|
||||
};
|
||||
}
|
||||
|
||||
private enum DesignNameUse
|
||||
{
|
||||
SaveCurrent,
|
||||
NewDesign,
|
||||
DuplicateDesign,
|
||||
NewFolder,
|
||||
FromClipboard,
|
||||
}
|
||||
|
||||
private void DrawDesignNamePopup(DesignNameUse use)
|
||||
{
|
||||
if (ImGui.BeginPopup($"{DesignNamePopupLabel}{use}"))
|
||||
{
|
||||
if (ImGui.InputText("##designName", ref _newDesignName, 64, ImGuiInputTextFlags.EnterReturnsTrue)
|
||||
&& _newDesignName.Any())
|
||||
{
|
||||
switch (use)
|
||||
{
|
||||
case DesignNameUse.SaveCurrent:
|
||||
SaveNewDesign(ConditionalCopy(_currentSave, _holdShift, _holdCtrl));
|
||||
break;
|
||||
case DesignNameUse.NewDesign:
|
||||
var empty = new CharacterSave();
|
||||
empty.Load(CharacterCustomization.Default);
|
||||
empty.WriteCustomizations = false;
|
||||
SaveNewDesign(empty);
|
||||
break;
|
||||
case DesignNameUse.DuplicateDesign:
|
||||
SaveNewDesign(ConditionalCopy(_selection!.Data, _holdShift, _holdCtrl));
|
||||
break;
|
||||
case DesignNameUse.NewFolder:
|
||||
_designs.FileSystem
|
||||
.CreateAllFolders($"{_newDesignName}/a"); // Filename is just ignored, but all folders are created.
|
||||
break;
|
||||
case DesignNameUse.FromClipboard:
|
||||
try
|
||||
{
|
||||
var text = ImGui.GetClipboardText();
|
||||
var save = CharacterSave.FromString(text);
|
||||
SaveNewDesign(save);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Information($"Could not save new Design from Clipboard:\n{e}");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
_newDesignName = string.Empty;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
if (_keyboardFocus)
|
||||
{
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
_keyboardFocus = false;
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenDesignNamePopup(DesignNameUse use)
|
||||
{
|
||||
_newDesignName = string.Empty;
|
||||
_keyboardFocus = true;
|
||||
_holdCtrl = ImGui.GetIO().KeyCtrl;
|
||||
_holdShift = ImGui.GetIO().KeyShift;
|
||||
ImGui.OpenPopup($"{DesignNamePopupLabel}{use}");
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
//
|
||||
// private enum DesignNameUse
|
||||
// {
|
||||
// SaveCurrent,
|
||||
// NewDesign,
|
||||
// DuplicateDesign,
|
||||
// NewFolder,
|
||||
// FromClipboard,
|
||||
// }
|
||||
//
|
||||
// private void DrawDesignNamePopup(DesignNameUse use)
|
||||
// {
|
||||
// if (ImGui.BeginPopup($"{DesignNamePopupLabel}{use}"))
|
||||
// {
|
||||
// if (ImGui.InputText("##designName", ref _newDesignName, 64, ImGuiInputTextFlags.EnterReturnsTrue)
|
||||
// && _newDesignName.Any())
|
||||
// {
|
||||
// switch (use)
|
||||
// {
|
||||
// case DesignNameUse.SaveCurrent:
|
||||
// SaveNewDesign(ConditionalCopy(_currentSave, _holdShift, _holdCtrl));
|
||||
// break;
|
||||
// case DesignNameUse.NewDesign:
|
||||
// var empty = new CharacterSave();
|
||||
// empty.Load(CharacterCustomization.Default);
|
||||
// empty.WriteCustomizations = false;
|
||||
// SaveNewDesign(empty);
|
||||
// break;
|
||||
// case DesignNameUse.DuplicateDesign:
|
||||
// SaveNewDesign(ConditionalCopy(_selection!.Data, _holdShift, _holdCtrl));
|
||||
// break;
|
||||
// case DesignNameUse.NewFolder:
|
||||
// _designs.FileSystem
|
||||
// .CreateAllFolders($"{_newDesignName}/a"); // Filename is just ignored, but all folders are created.
|
||||
// break;
|
||||
// case DesignNameUse.FromClipboard:
|
||||
// try
|
||||
// {
|
||||
// var text = ImGui.GetClipboardText();
|
||||
// var save = CharacterSave.FromString(text);
|
||||
// SaveNewDesign(save);
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// PluginLog.Information($"Could not save new Design from Clipboard:\n{e}");
|
||||
// }
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// _newDesignName = string.Empty;
|
||||
// ImGui.CloseCurrentPopup();
|
||||
// }
|
||||
//
|
||||
// if (_keyboardFocus)
|
||||
// {
|
||||
// ImGui.SetKeyboardFocusHere();
|
||||
// _keyboardFocus = false;
|
||||
// }
|
||||
//
|
||||
// ImGui.EndPopup();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void OpenDesignNamePopup(DesignNameUse use)
|
||||
// {
|
||||
// _newDesignName = string.Empty;
|
||||
// _keyboardFocus = true;
|
||||
// _holdCtrl = ImGui.GetIO().KeyCtrl;
|
||||
// _holdShift = ImGui.GetIO().KeyShift;
|
||||
// ImGui.OpenPopup($"{DesignNamePopupLabel}{use}");
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -4,92 +4,81 @@ using System.Reflection;
|
|||
using ImGuiNET;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Glamourer.Structs;
|
||||
using Item = Glamourer.Structs.Item;
|
||||
using Stain = Glamourer.Structs.Stain;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
{
|
||||
internal partial class Interface
|
||||
{
|
||||
private const float ColorButtonWidth = 22.5f;
|
||||
private const float ColorComboWidth = 140f;
|
||||
private const float ItemComboWidth = 350f;
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
private static readonly Vector4 GreyVector = new(0.5f, 0.5f, 0.5f, 1);
|
||||
//internal partial class Interface
|
||||
//{
|
||||
// private const float ColorButtonWidth = 22.5f;
|
||||
// private const float ColorComboWidth = 140f;
|
||||
// private const float ItemComboWidth = 350f;
|
||||
//
|
||||
// private static readonly Vector4 GreyVector = new(0.5f, 0.5f, 0.5f, 1);
|
||||
//
|
||||
// private static ComboWithFilter<Stain> CreateDefaultStainCombo(IReadOnlyList<Stain> stains)
|
||||
// => new("##StainCombo", ColorComboWidth, ColorButtonWidth, stains,
|
||||
// s => s.Name.ToString())
|
||||
// {
|
||||
// Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge,
|
||||
// PreList = () =>
|
||||
// {
|
||||
// ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
// ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
// ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
|
||||
// },
|
||||
// PostList = () => { ImGui.PopStyleVar(3); },
|
||||
// CreateSelectable = s =>
|
||||
// {
|
||||
// var push = PushColor(s);
|
||||
// var ret = ImGui.Button($"{s.Name}##Stain{(byte)s.RowIndex}",
|
||||
// Vector2.UnitX * (ColorComboWidth - ImGui.GetStyle().ScrollbarSize));
|
||||
// ImGui.PopStyleColor(push);
|
||||
// return ret;
|
||||
// },
|
||||
// ItemsAtOnce = 12,
|
||||
// };
|
||||
//
|
||||
// private ComboWithFilter<Item> CreateItemCombo(EquipSlot slot, IReadOnlyList<Item> items)
|
||||
// => new($"{_equipSlotNames[slot]}##Equip", ItemComboWidth, ItemComboWidth, items, i => i.Name)
|
||||
// {
|
||||
// Flags = ImGuiComboFlags.HeightLarge,
|
||||
// CreateSelectable = i =>
|
||||
// {
|
||||
// var ret = ImGui.Selectable(i.Name);
|
||||
// var setId = $"({(int)i.MainModel.id})";
|
||||
// var size = ImGui.CalcTextSize(setId).X;
|
||||
// ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - size - ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
// ImGui.TextColored(GreyVector, setId);
|
||||
// return ret;
|
||||
// },
|
||||
// };
|
||||
//
|
||||
// private (ComboWithFilter<Item>, ComboWithFilter<Stain>) CreateCombos(EquipSlot slot, IReadOnlyList<Item> items,
|
||||
// ComboWithFilter<Stain> defaultStain)
|
||||
// => (CreateItemCombo(slot, items), new ComboWithFilter<Stain>($"##{slot}Stain", defaultStain));
|
||||
//
|
||||
|
||||
private static ComboWithFilter<Stain> CreateDefaultStainCombo(IReadOnlyList<Stain> stains)
|
||||
=> new("##StainCombo", ColorComboWidth, ColorButtonWidth, stains,
|
||||
s => s.Name.ToString())
|
||||
{
|
||||
Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge,
|
||||
PreList = () =>
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
|
||||
},
|
||||
PostList = () => { ImGui.PopStyleVar(3); },
|
||||
CreateSelectable = s =>
|
||||
{
|
||||
var push = PushColor(s);
|
||||
var ret = ImGui.Button($"{s.Name}##Stain{(byte) s.RowIndex}",
|
||||
Vector2.UnitX * (ColorComboWidth - ImGui.GetStyle().ScrollbarSize));
|
||||
ImGui.PopStyleColor(push);
|
||||
return ret;
|
||||
},
|
||||
ItemsAtOnce = 12,
|
||||
};
|
||||
|
||||
private ComboWithFilter<Item> CreateItemCombo(EquipSlot slot, IReadOnlyList<Item> items)
|
||||
=> new($"{_equipSlotNames[slot]}##Equip", ItemComboWidth, ItemComboWidth, items, i => i.Name)
|
||||
{
|
||||
Flags = ImGuiComboFlags.HeightLarge,
|
||||
CreateSelectable = i =>
|
||||
{
|
||||
var ret = ImGui.Selectable(i.Name);
|
||||
var setId = $"({(int) i.MainModel.id})";
|
||||
var size = ImGui.CalcTextSize(setId).X;
|
||||
ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - size - ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
ImGui.TextColored(GreyVector, setId);
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
|
||||
private (ComboWithFilter<Item>, ComboWithFilter<Stain>) CreateCombos(EquipSlot slot, IReadOnlyList<Item> items,
|
||||
ComboWithFilter<Stain> defaultStain)
|
||||
=> (CreateItemCombo(slot, items), new ComboWithFilter<Stain>($"##{slot}Stain", defaultStain));
|
||||
|
||||
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon()
|
||||
{
|
||||
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
|
||||
if (resource != null)
|
||||
{
|
||||
var rawImage = new byte[resource.Length];
|
||||
resource.Read(rawImage, 0, (int) resource.Length);
|
||||
return Dalamud.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Dictionary<EquipSlot, string> GetEquipSlotNames()
|
||||
{
|
||||
var sheet = Dalamud.GameData.GetExcelSheet<Addon>()!;
|
||||
var ret = new Dictionary<EquipSlot, string>(12)
|
||||
{
|
||||
[EquipSlot.MainHand] = sheet.GetRow(738)?.Text.ToString() ?? "Main Hand",
|
||||
[EquipSlot.OffHand] = sheet.GetRow(739)?.Text.ToString() ?? "Off Hand",
|
||||
[EquipSlot.Head] = sheet.GetRow(740)?.Text.ToString() ?? "Head",
|
||||
[EquipSlot.Body] = sheet.GetRow(741)?.Text.ToString() ?? "Body",
|
||||
[EquipSlot.Hands] = sheet.GetRow(742)?.Text.ToString() ?? "Hands",
|
||||
[EquipSlot.Legs] = sheet.GetRow(744)?.Text.ToString() ?? "Legs",
|
||||
[EquipSlot.Feet] = sheet.GetRow(745)?.Text.ToString() ?? "Feet",
|
||||
[EquipSlot.Ears] = sheet.GetRow(746)?.Text.ToString() ?? "Ears",
|
||||
[EquipSlot.Neck] = sheet.GetRow(747)?.Text.ToString() ?? "Neck",
|
||||
[EquipSlot.Wrists] = sheet.GetRow(748)?.Text.ToString() ?? "Wrists",
|
||||
[EquipSlot.RFinger] = sheet.GetRow(749)?.Text.ToString() ?? "Right Ring",
|
||||
[EquipSlot.LFinger] = sheet.GetRow(750)?.Text.ToString() ?? "Left Ring",
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// private static Dictionary<EquipSlot, string> GetEquipSlotNames()
|
||||
// {
|
||||
// var sheet = Dalamud.GameData.GetExcelSheet<Addon>()!;
|
||||
// var ret = new Dictionary<EquipSlot, string>(12)
|
||||
// {
|
||||
// [EquipSlot.MainHand] = sheet.GetRow(738)?.Text.ToString() ?? "Main Hand",
|
||||
// [EquipSlot.OffHand] = sheet.GetRow(739)?.Text.ToString() ?? "Off Hand",
|
||||
// [EquipSlot.Head] = sheet.GetRow(740)?.Text.ToString() ?? "Head",
|
||||
// [EquipSlot.Body] = sheet.GetRow(741)?.Text.ToString() ?? "Body",
|
||||
// [EquipSlot.Hands] = sheet.GetRow(742)?.Text.ToString() ?? "Hands",
|
||||
// [EquipSlot.Legs] = sheet.GetRow(744)?.Text.ToString() ?? "Legs",
|
||||
// [EquipSlot.Feet] = sheet.GetRow(745)?.Text.ToString() ?? "Feet",
|
||||
// [EquipSlot.Ears] = sheet.GetRow(746)?.Text.ToString() ?? "Ears",
|
||||
// [EquipSlot.Neck] = sheet.GetRow(747)?.Text.ToString() ?? "Neck",
|
||||
// [EquipSlot.Wrists] = sheet.GetRow(748)?.Text.ToString() ?? "Wrists",
|
||||
// [EquipSlot.RFinger] = sheet.GetRow(749)?.Text.ToString() ?? "Right Ring",
|
||||
// [EquipSlot.LFinger] = sheet.GetRow(750)?.Text.ToString() ?? "Left Ring",
|
||||
// };
|
||||
// return ret;
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -4,60 +4,49 @@ using ImGuiNET;
|
|||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private static bool DrawCheckMark(string label, bool value, Action<bool> setter)
|
||||
{
|
||||
var startValue = value;
|
||||
if (ImGui.Checkbox(label, ref startValue) && startValue != value)
|
||||
{
|
||||
setter(startValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool DrawMiscellaneous(CharacterSave save, Character? player)
|
||||
{
|
||||
var ret = false;
|
||||
if (!ImGui.CollapsingHeader("Miscellaneous"))
|
||||
return ret;
|
||||
|
||||
ret |= DrawCheckMark("Hat Visible", save.HatState, v =>
|
||||
{
|
||||
save.HatState = v;
|
||||
player?.SetHatVisible(v);
|
||||
});
|
||||
|
||||
ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v =>
|
||||
{
|
||||
save.WeaponState = v;
|
||||
player?.SetWeaponHidden(!v);
|
||||
});
|
||||
|
||||
ret |= DrawCheckMark("Visor Toggled", save.VisorState, v =>
|
||||
{
|
||||
save.VisorState = v;
|
||||
player?.SetVisorToggled(v);
|
||||
});
|
||||
|
||||
ret |= DrawCheckMark("Is Wet", save.IsWet, v =>
|
||||
{
|
||||
save.IsWet = v;
|
||||
player?.SetWetness(v);
|
||||
});
|
||||
|
||||
var alpha = save.Alpha;
|
||||
if (ImGui.DragFloat("Alpha", ref alpha, 0.01f, 0f, 1f, "%.2f") && alpha != save.Alpha)
|
||||
{
|
||||
alpha = (float)Math.Round(alpha > 1 ? 1 : alpha < 0 ? 0 : alpha, 2);
|
||||
save.Alpha = alpha;
|
||||
ret = true;
|
||||
if (player != null)
|
||||
player.Alpha() = alpha;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
//internal partial class Interface
|
||||
//{
|
||||
//
|
||||
// private static bool DrawMiscellaneous(CharacterSave save, Character? player)
|
||||
// {
|
||||
// var ret = false;
|
||||
// if (!ImGui.CollapsingHeader("Miscellaneous"))
|
||||
// return ret;
|
||||
//
|
||||
// ret |= DrawCheckMark("Hat Visible", save.HatState, v =>
|
||||
// {
|
||||
// save.HatState = v;
|
||||
// player?.SetHatVisible(v);
|
||||
// });
|
||||
//
|
||||
// ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v =>
|
||||
// {
|
||||
// save.WeaponState = v;
|
||||
// player?.SetWeaponHidden(!v);
|
||||
// });
|
||||
//
|
||||
// ret |= DrawCheckMark("Visor Toggled", save.VisorState, v =>
|
||||
// {
|
||||
// save.VisorState = v;
|
||||
// player?.SetVisorToggled(v);
|
||||
// });
|
||||
//
|
||||
// ret |= DrawCheckMark("Is Wet", save.IsWet, v =>
|
||||
// {
|
||||
// save.IsWet = v;
|
||||
// player?.SetWetness(v);
|
||||
// });
|
||||
//
|
||||
// var alpha = save.Alpha;
|
||||
// if (ImGui.DragFloat("Alpha", ref alpha, 0.01f, 0f, 1f, "%.2f") && alpha != save.Alpha)
|
||||
// {
|
||||
// alpha = (float)Math.Round(alpha > 1 ? 1 : alpha < 0 ? 0 : alpha, 2);
|
||||
// save.Alpha = alpha;
|
||||
// ret = true;
|
||||
// if (player != null)
|
||||
// player.Alpha() = alpha;
|
||||
// }
|
||||
//
|
||||
// return ret;
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -6,82 +6,82 @@ using OtterGui.Raii;
|
|||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private string? _currentRevertableName;
|
||||
private CharacterSave? _currentRevertable;
|
||||
|
||||
private void DrawRevertablesSelector()
|
||||
{
|
||||
ImGui.BeginGroup();
|
||||
DrawPlayerFilter();
|
||||
if (!ImGui.BeginChild("##playerSelector",
|
||||
new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
||||
{
|
||||
ImGui.EndChild();
|
||||
ImGui.EndGroup();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (name, save) in Glamourer.RevertableDesigns.Saves)
|
||||
{
|
||||
if (name.ToLowerInvariant().Contains(_playerFilterLower) && ImGui.Selectable(name, name == _currentRevertableName))
|
||||
{
|
||||
_currentRevertableName = name;
|
||||
_currentRevertable = save;
|
||||
}
|
||||
}
|
||||
|
||||
using (var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
||||
{
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
DrawSelectionButtons();
|
||||
ImGui.EndGroup();
|
||||
}
|
||||
|
||||
private void DrawRevertablePanel()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
{
|
||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, GreenHeaderColor)
|
||||
.Push(ImGuiCol.Button, buttonColor)
|
||||
.Push(ImGuiCol.ButtonHovered, buttonColor)
|
||||
.Push(ImGuiCol.ButtonActive, buttonColor);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
ImGui.Button($"{_currentRevertableName}##playerHeader", -Vector2.UnitX * 0.0001f);
|
||||
}
|
||||
|
||||
if (!ImGui.BeginChild("##revertableData", -Vector2.One, true))
|
||||
{
|
||||
ImGui.EndChild();
|
||||
return;
|
||||
}
|
||||
|
||||
var save = _currentRevertable!.Copy();
|
||||
DrawCustomization(ref save.Customizations);
|
||||
DrawEquip(save.Equipment);
|
||||
DrawMiscellaneous(save, null);
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void DrawRevertablesTab()
|
||||
{
|
||||
using var tabItem = ImRaii.TabItem("Revertables");
|
||||
if (!tabItem)
|
||||
return;
|
||||
|
||||
DrawRevertablesSelector();
|
||||
|
||||
if (_currentRevertableName == null)
|
||||
return;
|
||||
|
||||
ImGui.SameLine();
|
||||
DrawRevertablePanel();
|
||||
}
|
||||
}
|
||||
//internal partial class Interface
|
||||
//{
|
||||
// private string? _currentRevertableName;
|
||||
// private CharacterSave? _currentRevertable;
|
||||
//
|
||||
// private void DrawRevertablesSelector()
|
||||
// {
|
||||
// ImGui.BeginGroup();
|
||||
// DrawPlayerFilter();
|
||||
// if (!ImGui.BeginChild("##playerSelector",
|
||||
// new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
||||
// {
|
||||
// ImGui.EndChild();
|
||||
// ImGui.EndGroup();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// foreach (var (name, save) in Glamourer.RevertableDesigns.Saves)
|
||||
// {
|
||||
// if (name.ToLowerInvariant().Contains(_playerFilterLower) && ImGui.Selectable(name, name == _currentRevertableName))
|
||||
// {
|
||||
// _currentRevertableName = name;
|
||||
// _currentRevertable = save;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// using (var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
||||
// {
|
||||
// ImGui.EndChild();
|
||||
// }
|
||||
//
|
||||
// DrawSelectionButtons();
|
||||
// ImGui.EndGroup();
|
||||
// }
|
||||
//
|
||||
// private void DrawRevertablePanel()
|
||||
// {
|
||||
// using var group = ImRaii.Group();
|
||||
// {
|
||||
// var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||
// using var color = ImRaii.PushColor(ImGuiCol.Text, GreenHeaderColor)
|
||||
// .Push(ImGuiCol.Button, buttonColor)
|
||||
// .Push(ImGuiCol.ButtonHovered, buttonColor)
|
||||
// .Push(ImGuiCol.ButtonActive, buttonColor);
|
||||
// using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
// .Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
// ImGui.Button($"{_currentRevertableName}##playerHeader", -Vector2.UnitX * 0.0001f);
|
||||
// }
|
||||
//
|
||||
// if (!ImGui.BeginChild("##revertableData", -Vector2.One, true))
|
||||
// {
|
||||
// ImGui.EndChild();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var save = _currentRevertable!.Copy();
|
||||
// DrawCustomization(ref save.Customizations);
|
||||
// DrawEquip(save.Equipment);
|
||||
// DrawMiscellaneous(save, null);
|
||||
//
|
||||
// ImGui.EndChild();
|
||||
// }
|
||||
//
|
||||
// [Conditional("DEBUG")]
|
||||
// private void DrawRevertablesTab()
|
||||
// {
|
||||
// using var tabItem = ImRaii.TabItem("Revertables");
|
||||
// if (!tabItem)
|
||||
// return;
|
||||
//
|
||||
// DrawRevertablesSelector();
|
||||
//
|
||||
// if (_currentRevertableName == null)
|
||||
// return;
|
||||
//
|
||||
// ImGui.SameLine();
|
||||
// DrawRevertablePanel();
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
179
Glamourer/ObjectManager.cs
Normal file
179
Glamourer/ObjectManager.cs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public unsafe struct Actor : IEquatable<Actor>
|
||||
{
|
||||
public static readonly Actor Null = new() { Pointer = null };
|
||||
|
||||
public FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Pointer;
|
||||
|
||||
public IntPtr Address
|
||||
=> (IntPtr)Pointer;
|
||||
|
||||
public static implicit operator Actor(IntPtr? pointer)
|
||||
=> new() { Pointer = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)pointer.GetValueOrDefault(IntPtr.Zero) };
|
||||
|
||||
public static implicit operator IntPtr(Actor actor)
|
||||
=> actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer;
|
||||
|
||||
public Character? Character
|
||||
=> Pointer == null ? null : Dalamud.Objects[Pointer->GameObject.ObjectIndex] as Character;
|
||||
|
||||
public bool IsAvailable
|
||||
=> Pointer->GameObject.GetIsTargetable();
|
||||
|
||||
public bool IsHuman
|
||||
=> Pointer != null && Pointer->ModelCharaId == 0;
|
||||
|
||||
public int ModelId
|
||||
=> Pointer != null ? Pointer->ModelCharaId : 0;
|
||||
|
||||
public void SetModelId(int value)
|
||||
{
|
||||
if (Pointer != null)
|
||||
Pointer->ModelCharaId = value;
|
||||
}
|
||||
|
||||
public static implicit operator bool(Actor actor)
|
||||
=> actor.Pointer != null;
|
||||
|
||||
public static bool operator true(Actor actor)
|
||||
=> actor.Pointer != null;
|
||||
|
||||
public static bool operator false(Actor actor)
|
||||
=> actor.Pointer == null;
|
||||
|
||||
public static bool operator !(Actor actor)
|
||||
=> actor.Pointer == null;
|
||||
|
||||
public bool Equals(Actor other)
|
||||
=> Pointer == other.Pointer;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is Actor other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> ((ulong)Pointer).GetHashCode();
|
||||
|
||||
public static bool operator ==(Actor lhs, Actor rhs)
|
||||
=> lhs.Pointer == rhs.Pointer;
|
||||
|
||||
public static bool operator !=(Actor lhs, Actor rhs)
|
||||
=> lhs.Pointer != rhs.Pointer;
|
||||
}
|
||||
|
||||
public static class ObjectManager
|
||||
{
|
||||
private const int GPosePlayerIndex = 201;
|
||||
private const int CharacterScreenIndex = 240;
|
||||
private const int ExamineScreenIndex = 241;
|
||||
private const int FittingRoomIndex = 242;
|
||||
private const int DyePreviewIndex = 243;
|
||||
private static readonly Dictionary<string, int> _nameCounters = new();
|
||||
private static readonly Dictionary<string, Actor> _gPoseActors = new(CharacterScreenIndex - GPosePlayerIndex);
|
||||
|
||||
public static bool IsInGPose()
|
||||
=> Dalamud.Objects[GPosePlayerIndex] != null;
|
||||
|
||||
public static Actor GPosePlayer
|
||||
=> Dalamud.Objects[GPosePlayerIndex]?.Address;
|
||||
|
||||
public static Actor Player
|
||||
=> Dalamud.ClientState.LocalPlayer?.Address;
|
||||
|
||||
public record struct ActorData(string Label, string Name, Actor Actor, bool Modifiable, Actor GPose);
|
||||
|
||||
public static IEnumerable<ActorData> GetEnumerator()
|
||||
{
|
||||
_nameCounters.Clear();
|
||||
_gPoseActors.Clear();
|
||||
for (var i = GPosePlayerIndex; i < CharacterScreenIndex; ++i)
|
||||
{
|
||||
var character = Dalamud.Objects[i];
|
||||
if (character == null)
|
||||
break;
|
||||
|
||||
var name = character.Name.TextValue;
|
||||
_gPoseActors[name] = character.Address;
|
||||
yield return new ActorData(GetLabel(character, name, 0, true), name, character.Address, true, Actor.Null);
|
||||
}
|
||||
|
||||
var actor = Dalamud.Objects[CharacterScreenIndex];
|
||||
if (actor != null)
|
||||
yield return new ActorData("Character Screen Actor", string.Empty, actor.Address, false, Actor.Null);
|
||||
|
||||
actor = Dalamud.Objects[ExamineScreenIndex];
|
||||
if (actor != null)
|
||||
yield return new ActorData("Examine Screen Actor", string.Empty, actor.Address, false, Actor.Null);
|
||||
|
||||
actor = Dalamud.Objects[FittingRoomIndex];
|
||||
if (actor != null)
|
||||
yield return new ActorData("Fitting Room Actor", string.Empty, actor.Address, false, Actor.Null);
|
||||
|
||||
actor = Dalamud.Objects[DyePreviewIndex];
|
||||
if (actor != null)
|
||||
yield return new ActorData("Dye Preview Actor", string.Empty, actor.Address, false, Actor.Null);
|
||||
|
||||
for (var i = 0; i < GPosePlayerIndex; ++i)
|
||||
{
|
||||
var character = Dalamud.Objects[i];
|
||||
if (character == null
|
||||
|| character.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion
|
||||
or ObjectKind.Retainer))
|
||||
continue;
|
||||
|
||||
var name = character.Name.TextValue;
|
||||
if (name.Length == 0)
|
||||
continue;
|
||||
|
||||
if (_nameCounters.TryGetValue(name, out var num))
|
||||
_nameCounters[name] = ++num;
|
||||
else
|
||||
_nameCounters[name] = num = 1;
|
||||
|
||||
if (!_gPoseActors.TryGetValue(name, out var gPose))
|
||||
gPose = Actor.Null;
|
||||
|
||||
yield return new ActorData(GetLabel(character, name, num, false), name, character.Address, true, gPose);
|
||||
}
|
||||
|
||||
for (var i = DyePreviewIndex + 1; i < Dalamud.Objects.Length; ++i)
|
||||
{
|
||||
var character = Dalamud.Objects[i];
|
||||
if (character == null
|
||||
|| !((Actor)character.Address).IsAvailable
|
||||
|| character.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion
|
||||
or ObjectKind.Retainer))
|
||||
continue;
|
||||
|
||||
var name = character.Name.TextValue;
|
||||
if (name.Length == 0)
|
||||
continue;
|
||||
|
||||
if (_nameCounters.TryGetValue(name, out var num))
|
||||
_nameCounters[name] = ++num;
|
||||
else
|
||||
_nameCounters[name] = num = 1;
|
||||
|
||||
if (!_gPoseActors.TryGetValue(name, out var gPose))
|
||||
gPose = Actor.Null;
|
||||
|
||||
yield return new ActorData(GetLabel(character, name, num, false), name, character.Address, true, gPose);
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe string GetLabel(GameObject player, string playerName, int num, bool gPose)
|
||||
{
|
||||
if (player.ObjectKind == ObjectKind.Player)
|
||||
return gPose ? $"{playerName} (GPose)" : num == 1 ? playerName : $"{playerName} #{num}";
|
||||
|
||||
if (((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player!.Address)->ModelCharaId == 0)
|
||||
return gPose ? $"{playerName} (GPose, NPC)" : num == 1 ? $"{playerName} (NPC)" : $"{playerName} #{num} (NPC)";
|
||||
|
||||
return gPose ? $"{playerName} (GPose, Monster)" : num == 1 ? $"{playerName} (Monster)" : $"{playerName} #{num} (Monster)";
|
||||
}
|
||||
}
|
||||
211
Glamourer/RedrawManager.cs
Normal file
211
Glamourer/RedrawManager.cs
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public unsafe class RedrawManager : IDisposable
|
||||
{
|
||||
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data);
|
||||
|
||||
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A")]
|
||||
public Hook<FlagSlotForUpdateDelegate> FlagSlotForUpdateHook = null!;
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slot, CharacterArmor* data)
|
||||
{
|
||||
return FlagSlotForUpdateHook.Original(drawObject, slot, data);
|
||||
}
|
||||
|
||||
public delegate void LoadWeaponDelegate(IntPtr characterOffset, uint slot, ulong data, byte unk);
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? 44 8B 9F")]
|
||||
public Hook<LoadWeaponDelegate> LoadWeaponHook = null!;
|
||||
|
||||
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong data, byte unk)
|
||||
{
|
||||
const int offset = 0xD8 * 8;
|
||||
PluginLog.Information($"0x{characterOffset:X}, 0x{characterOffset - offset:X}, {slot}, {data:16X}, {unk}");
|
||||
LoadWeaponHook.Original(characterOffset, slot, data, unk);
|
||||
}
|
||||
|
||||
//[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A",
|
||||
// DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
//public Hook<FlagSlotForUpdateDelegate>? FlagSlotForUpdateHook;
|
||||
//
|
||||
// public readonly FixedDesigns FixedDesigns;
|
||||
//
|
||||
|
||||
private readonly Dictionary<string, CharacterSave> _currentRedraws = new(32);
|
||||
|
||||
public RedrawManager()
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
// FixedDesigns = new FixedDesigns(designs);
|
||||
Glamourer.Penumbra.CreatingCharacterBase += OnCharacterRedraw;
|
||||
FlagSlotForUpdateHook.Enable();
|
||||
LoadWeaponHook.Enable();
|
||||
//
|
||||
// if (Glamourer.Config.ApplyFixedDesigns)
|
||||
// Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
FlagSlotForUpdateHook.Dispose();
|
||||
LoadWeaponHook.Dispose();
|
||||
Glamourer.Penumbra.CreatingCharacterBase -= OnCharacterRedraw;
|
||||
//FlagSlotForUpdateHook?.Dispose();
|
||||
}
|
||||
|
||||
public void Set(Character* actor, CharacterSave save)
|
||||
{
|
||||
var name = GetName(actor);
|
||||
if (name.Length == 0)
|
||||
return;
|
||||
|
||||
_currentRedraws[name] = save;
|
||||
}
|
||||
|
||||
public void Set(IntPtr actor, CharacterSave save)
|
||||
=> Set((Character*)actor, save);
|
||||
|
||||
public void Revert(Character* actor)
|
||||
=> _currentRedraws.Remove(GetName(actor));
|
||||
|
||||
public void Revert(IntPtr actor)
|
||||
=> Revert((Character*)actor);
|
||||
|
||||
private static string GetName(Character* actor)
|
||||
{
|
||||
return string.Concat(new Utf8String(actor->GameObject.Name)
|
||||
.Select(c => (char)c)
|
||||
.Append(actor->GameObject.ObjectKind == (byte)ObjectKind.Pc ? (char)actor->HomeWorld : (char)actor->GameObject.ObjectIndex));
|
||||
}
|
||||
|
||||
private void Cleanup(object? _, ushort _1)
|
||||
=> _currentRedraws.Clear();
|
||||
|
||||
public void ChangeEquip(Human* actor, EquipSlot slot, CharacterArmor item)
|
||||
=> Flag(actor, slot.ToIndex(), &item);
|
||||
|
||||
public void ChangeEquip(Character* character, EquipSlot slot, CharacterArmor item)
|
||||
=> ChangeEquip((Human*)character->GameObject.DrawObject, slot, item);
|
||||
|
||||
public void ChangeEquip(IntPtr character, EquipSlot slot, CharacterArmor item)
|
||||
=> ChangeEquip((Character*)character, slot, item);
|
||||
|
||||
private void OnCharacterRedraw(IntPtr addr, IntPtr modelId, IntPtr customize, IntPtr equipData)
|
||||
{
|
||||
var name = GetName((Character*)addr);
|
||||
if (_currentRedraws.TryGetValue(name, out var save))
|
||||
{
|
||||
*(CustomizationData*)customize = *(CustomizationData*)save.Customize.Address;
|
||||
var equip = (CharacterEquip)equipData;
|
||||
var newEquip = save.Equipment;
|
||||
for (var i = 0; i < 10; ++i)
|
||||
equip[i] = newEquip[i];
|
||||
}
|
||||
|
||||
|
||||
//*(uint*)modelId = 0;
|
||||
|
||||
//var human = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)addr;
|
||||
//if (human->GameObject.ObjectKind is (byte)ObjectKind.EventNpc or (byte)ObjectKind.BattleNpc or (byte)ObjectKind.Player
|
||||
// && human->ModelCharaId == 0)
|
||||
//{
|
||||
// var name = new Utf8String(human->GameObject.Name).ToString();
|
||||
// if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs))
|
||||
// {
|
||||
// var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(human->ClassJob));
|
||||
// if (design != null)
|
||||
// {
|
||||
// if (design.Design.Data.WriteCustomizations)
|
||||
// *(CharacterCustomization*)customize = design.Design.Data.Customizations;
|
||||
//
|
||||
// var data = (uint*)equipData;
|
||||
// for (var i = 0u; i < 10; ++i)
|
||||
// {
|
||||
// var slot = i.ToEquipSlot();
|
||||
// if (design.Design.Data.WriteEquipment.Fits(slot))
|
||||
// data[i] = slot switch
|
||||
// {
|
||||
// EquipSlot.Head => design.Design.Data.Equipment.Head.Value,
|
||||
// EquipSlot.Body => design.Design.Data.Equipment.Body.Value,
|
||||
// EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value,
|
||||
// EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value,
|
||||
// EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value,
|
||||
// EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value,
|
||||
// EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value,
|
||||
// EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value,
|
||||
// EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value,
|
||||
// EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value,
|
||||
// _ => 0,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
//
|
||||
// private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, uint* data)
|
||||
// {
|
||||
// ulong ret;
|
||||
// var slot = slotIdx.ToEquipSlot();
|
||||
// try
|
||||
// {
|
||||
// if (slot != EquipSlot.Unknown)
|
||||
// {
|
||||
// var gameObject =
|
||||
// (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||
// if (gameObject != null)
|
||||
// {
|
||||
// var name = new Utf8String(gameObject->GameObject.Name).ToString();
|
||||
// if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs))
|
||||
// {
|
||||
// var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(gameObject->ClassJob));
|
||||
// if (design != null && design.Design.Data.WriteEquipment.Fits(slot))
|
||||
// *data = slot switch
|
||||
// {
|
||||
// EquipSlot.Head => design.Design.Data.Equipment.Head.Value,
|
||||
// EquipSlot.Body => design.Design.Data.Equipment.Body.Value,
|
||||
// EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value,
|
||||
// EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value,
|
||||
// EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value,
|
||||
// EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value,
|
||||
// EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value,
|
||||
// EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value,
|
||||
// EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value,
|
||||
// EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value,
|
||||
// _ => 0,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// ret = FlagSlotForUpdateHook!.Original(drawObject, slotIdx, data);
|
||||
// }
|
||||
//
|
||||
// return ret;
|
||||
// }
|
||||
//
|
||||
// public void UpdateSlot(Human* drawObject, EquipSlot slot, CharacterArmor data)
|
||||
// {
|
||||
// var idx = slot.ToIndex();
|
||||
// if (idx >= 10)
|
||||
// return;
|
||||
//
|
||||
// FlagSlotForUpdateDetour(drawObject, idx, (uint*)&data);
|
||||
// }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue