This commit is contained in:
Ottermandias 2023-06-18 00:49:26 +02:00
parent 27f151c55a
commit d10cb3137f
14 changed files with 1366 additions and 373 deletions

View file

@ -0,0 +1,211 @@
using System;
using System.Linq;
using System.Security.AccessControl;
using Dalamud.Data;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Plugin;
using Glamourer.Customization;
using Penumbra.GameData.Enums;
namespace Glamourer.Services;
public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationManager>
{
public CustomizationService(DalamudPluginInterface pi, DataManager gameData)
: base(nameof(CustomizationService), () => CustomizationManager.Create(pi, gameData))
{ }
/// <summary> In languages other than english the actual clan name may depend on gender. </summary>
public 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) => AwaitedService.GetName(CustomName.MidlanderM),
(Gender.Male, SubRace.Highlander) => AwaitedService.GetName(CustomName.HighlanderM),
(Gender.Male, SubRace.Wildwood) => AwaitedService.GetName(CustomName.WildwoodM),
(Gender.Male, SubRace.Duskwight) => AwaitedService.GetName(CustomName.DuskwightM),
(Gender.Male, SubRace.Plainsfolk) => AwaitedService.GetName(CustomName.PlainsfolkM),
(Gender.Male, SubRace.Dunesfolk) => AwaitedService.GetName(CustomName.DunesfolkM),
(Gender.Male, SubRace.SeekerOfTheSun) => AwaitedService.GetName(CustomName.SeekerOfTheSunM),
(Gender.Male, SubRace.KeeperOfTheMoon) => AwaitedService.GetName(CustomName.KeeperOfTheMoonM),
(Gender.Male, SubRace.Seawolf) => AwaitedService.GetName(CustomName.SeawolfM),
(Gender.Male, SubRace.Hellsguard) => AwaitedService.GetName(CustomName.HellsguardM),
(Gender.Male, SubRace.Raen) => AwaitedService.GetName(CustomName.RaenM),
(Gender.Male, SubRace.Xaela) => AwaitedService.GetName(CustomName.XaelaM),
(Gender.Male, SubRace.Helion) => AwaitedService.GetName(CustomName.HelionM),
(Gender.Male, SubRace.Lost) => AwaitedService.GetName(CustomName.LostM),
(Gender.Male, SubRace.Rava) => AwaitedService.GetName(CustomName.RavaM),
(Gender.Male, SubRace.Veena) => AwaitedService.GetName(CustomName.VeenaM),
(Gender.Female, SubRace.Midlander) => AwaitedService.GetName(CustomName.MidlanderF),
(Gender.Female, SubRace.Highlander) => AwaitedService.GetName(CustomName.HighlanderF),
(Gender.Female, SubRace.Wildwood) => AwaitedService.GetName(CustomName.WildwoodF),
(Gender.Female, SubRace.Duskwight) => AwaitedService.GetName(CustomName.DuskwightF),
(Gender.Female, SubRace.Plainsfolk) => AwaitedService.GetName(CustomName.PlainsfolkF),
(Gender.Female, SubRace.Dunesfolk) => AwaitedService.GetName(CustomName.DunesfolkF),
(Gender.Female, SubRace.SeekerOfTheSun) => AwaitedService.GetName(CustomName.SeekerOfTheSunF),
(Gender.Female, SubRace.KeeperOfTheMoon) => AwaitedService.GetName(CustomName.KeeperOfTheMoonF),
(Gender.Female, SubRace.Seawolf) => AwaitedService.GetName(CustomName.SeawolfF),
(Gender.Female, SubRace.Hellsguard) => AwaitedService.GetName(CustomName.HellsguardF),
(Gender.Female, SubRace.Raen) => AwaitedService.GetName(CustomName.RaenF),
(Gender.Female, SubRace.Xaela) => AwaitedService.GetName(CustomName.XaelaF),
(Gender.Female, SubRace.Helion) => AwaitedService.GetName(CustomName.HelionM),
(Gender.Female, SubRace.Lost) => AwaitedService.GetName(CustomName.LostM),
(Gender.Female, SubRace.Rava) => AwaitedService.GetName(CustomName.RavaF),
(Gender.Female, SubRace.Veena) => AwaitedService.GetName(CustomName.VeenaF),
_ => "Unknown",
};
}
/// <summary>
/// Check that the given race and clan are valid.
/// The returned race and clan fit together and are valid.
/// The return value is an empty string if everything was correct and a warning otherwise.
/// </summary>
public string ValidateClan(SubRace clan, Race race, out Race actualRace, out SubRace actualClan)
{
if (AwaitedService.Clans.Contains(clan))
{
actualClan = clan;
actualRace = actualClan.ToRace();
if (race != actualRace)
return $"The race {race.ToName()} does not correspond to the clan {clan.ToName()}, changed to {actualRace.ToName()}.";
return string.Empty;
}
if (AwaitedService.Races.Contains(race))
{
actualRace = race;
actualClan = AwaitedService.Clans.FirstOrDefault(c => c.ToRace() == race, SubRace.Unknown);
// This should not happen.
if (actualClan == SubRace.Unknown)
{
actualRace = Race.Hyur;
actualClan = SubRace.Midlander;
return
$"The clan {clan.ToName()} is invalid and the race {race.ToName()} does not correspond to any clan, reset to {Race.Hyur.ToName()} {SubRace.Midlander.ToName()}.";
}
return $"The clan {clan.ToName()} is invalid, but the race {race.ToName()} is known, reset to {actualClan.ToName()}.";
}
actualRace = Race.Hyur;
actualClan = SubRace.Midlander;
return
$"Both the clan {clan.ToName()} and the race {race.ToName()} are invalid, reset to {Race.Hyur.ToName()} {SubRace.Midlander.ToName()}.";
}
/// <summary>
/// Check that the given gender is valid for that race.
/// The returned gender is valid for the race.
/// The return value is an empty string if everything was correct and a warning otherwise.
/// </summary>
public string ValidateGender(Race race, Gender gender, out Gender actualGender)
{
if (!AwaitedService.Genders.Contains(gender))
{
actualGender = Gender.Male;
return $"The gender {gender.ToName()} is unknown, reset to {Gender.Male.ToName()}.";
}
// TODO: Female Hrothgar
if (gender == Gender.Female && race == Race.Hrothgar)
{
actualGender = Gender.Male;
return $"{Race.Hrothgar.ToName()} do not currently support {Gender.Female.ToName()} characters, reset to {Gender.Male.ToName()}.";
}
actualGender = gender;
return string.Empty;
}
/// <summary>
/// Check that the given model id is valid.
/// The returned model id is 0.
/// The return value is an empty string if everything was correct and a warning otherwise.
/// </summary>
public string ValidateModelId(uint modelId, out uint actualModelId)
{
actualModelId = 0;
return modelId != 0 ? $"Model IDs different from 0 are not currently allowed, reset {modelId} to 0." : string.Empty;
}
/// <summary>
/// Validate a single customization value against a given set of race and gender (and face).
/// The returned actualValue is either the correct value or the one with index 0.
/// The return value is an empty string or a warning message.
/// </summary>
public static string ValidateCustomizeValue(CustomizationSet set, CustomizeValue face, CustomizeIndex index, CustomizeValue value,
out CustomizeValue actualValue)
{
var count = set.Count(index, face);
var idx = set.DataByValue(index, value, out var data, face);
if (idx >= 0 && idx < count)
{
actualValue = value;
return string.Empty;
}
var name = set.Option(index);
var newValue = set.Data(index, 0, face);
actualValue = newValue.Value;
return
$"Customization {name} for {set.Race.ToName()} {set.Gender.ToName()}s does not support value {value.Value}, reset to {newValue.Value.Value}.";
}
/// <summary> Change a clan while keeping all other customizations valid. </summary>
public bool ChangeClan(ref Customize customize, SubRace newClan)
{
if (customize.Clan == newClan)
return false;
if (ValidateClan(newClan, newClan.ToRace(), out var newRace, out newClan).Length > 0)
return false;
customize.Race = newRace;
customize.Clan = newClan;
// TODO Female Hrothgar
if (newRace == Race.Hrothgar)
customize.Gender = Gender.Male;
var set = AwaitedService.GetList(customize.Clan, customize.Gender);
FixValues(set, ref customize);
return true;
}
/// <summary> Change a gender while keeping all other customizations valid. </summary>
public bool ChangeGender(ref Customize customize, Gender newGender)
{
if (customize.Gender == newGender)
return false;
// TODO Female Hrothgar
if (customize.Race is Race.Hrothgar)
return false;
if (ValidateGender(customize.Race, newGender, out newGender).Length > 0)
return false;
customize.Gender = newGender;
var set = AwaitedService.GetList(customize.Clan, customize.Gender);
FixValues(set, ref customize);
return true;
}
private static void FixValues(CustomizationSet set, ref Customize customize)
{
foreach (var idx in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable))
{
if (ValidateCustomizeValue(set, customize.Face, idx, customize[idx], out var fixedValue).Length > 0)
customize[idx] = fixedValue;
}
}
}

View file

@ -22,6 +22,7 @@ public class FilenameService
DesignDirectory = Path.Combine(ConfigDirectory, "designs");
}
public IEnumerable<FileInfo> Designs()
{
if (!Directory.Exists(DesignDirectory))

View file

@ -75,7 +75,7 @@ public class ItemManager : IDisposable
if (itemId == SmallclothesId(slot))
return SmallClothesItem(slot);
if (!ItemService.AwaitedService.TryGetValue(itemId, slot is EquipSlot.MainHand, out var item))
if (!ItemService.AwaitedService.TryGetValue(itemId, slot is not EquipSlot.OffHand, out var item))
return new EquipItem(string.Intern($"Unknown #{itemId}"), itemId, 0, 0, 0, 0, 0);
if (item.Type.ToSlot() != slot)
@ -134,4 +134,88 @@ public class ItemManager : IDisposable
? item
: new EquipItem($"Unknown ({id.Value}-{type.Value}-{variant})", 0, 0, id, type, variant, 0);
}
/// <summary>
/// Check whether an item id resolves to an existing item of the correct slot (which should not be weapons.)
/// The returned item is either the resolved correct item, or the Nothing item for that slot.
/// The return value is an empty string if there was no problem and a warning otherwise.
/// </summary>
public string ValidateItem(EquipSlot slot, uint itemId, out EquipItem item)
{
if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
throw new Exception("Internal Error: Used armor functionality for weapons.");
item = Resolve(slot, itemId);
if (item.Valid)
return string.Empty;
item = NothingItem(slot);
return $"The {slot.ToName()} item {itemId} does not exist, reset to Nothing.";
}
/// <summary>
/// Check whether a stain id is an existing stain.
/// The returned stain id is either the input or 0.
/// The return value is an empty string if there was no problem and a warning otherwise.
/// </summary>
public string ValidateStain(StainId stain, out StainId ret)
{
if (stain.Value == 0 || Stains.ContainsKey(stain))
{
ret = stain;
return string.Empty;
}
ret = 0;
return $"The Stain {stain} does not exist, reset to unstained.";
}
/// <summary>
/// Check whether a combination of an item id for a mainhand and for an offhand is valid.
/// The returned items are either the resolved correct items,
/// the correct mainhand and an appropriate offhand (implicit offhand or nothing),
/// or the default sword and a nothing offhand.
/// The return value is an empty string if there was no problem and a warning otherwise.
/// </summary>
public string ValidateWeapons(uint mainId, uint offId, out EquipItem main, out EquipItem off)
{
var ret = string.Empty;
main = Resolve(EquipSlot.MainHand, mainId);
if (!main.Valid)
{
main = DefaultSword;
ret = $"The mainhand weapon {mainId} does not exist, reset to default sword.";
}
var offhandType = main.Type.Offhand();
off = Resolve(offhandType, offId);
if (off.Valid)
return ret;
// Try implicit offhand.
off = Resolve(offhandType, mainId);
if (off.Valid)
{
// Can not be set to default sword before because then it could not be valid.
ret = $"The offhand weapon {offId} does not exist, reset to implied offhand.";
}
else
{
if (FullEquipTypeExtensions.OffhandTypes.Contains(offhandType))
{
main = DefaultSword;
off = NothingItem(FullEquipType.Shield);
ret =
$"The offhand weapon {offId} does not exist, but no default could be restored, reset mainhand to default sword and offhand to nothing.";
}
else
{
off = NothingItem(offhandType);
if (ret.Length == 0)
ret = $"The offhand weapon {offId} does not exist, reset to no offhand.";
}
}
return ret;
}
}

View file

@ -1,4 +1,5 @@
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui;
using Glamourer.Gui.Tabs;
@ -21,6 +22,7 @@ public static class ServiceManager
.AddInterop()
.AddEvents()
.AddData()
.AddDesigns()
.AddUi()
.AddApi();
@ -44,7 +46,8 @@ public static class ServiceManager
private static IServiceCollection AddEvents(this IServiceCollection services)
=> services.AddSingleton<VisorStateChanged>()
.AddSingleton<UpdatedSlot>();
.AddSingleton<UpdatedSlot>()
.AddSingleton<DesignChanged>();
private static IServiceCollection AddData(this IServiceCollection services)
=> services.AddSingleton<IdentifierService>()
@ -61,6 +64,10 @@ public static class ServiceManager
.AddSingleton<PenumbraService>()
.AddSingleton<ObjectManager>();
private static IServiceCollection AddDesigns(this IServiceCollection services)
=> services.AddSingleton<DesignManager>()
.AddSingleton<DesignFileSystem>();
private static IServiceCollection AddUi(this IServiceCollection services)
=> services.AddSingleton<DebugTab>()
.AddSingleton<MainWindow>()

View file

@ -7,11 +7,9 @@ using Penumbra.GameData.Actors;
using System;
using System.Threading.Tasks;
using Dalamud.Game;
using Glamourer.Customization;
using Glamourer.Interop.Penumbra;
using Penumbra.GameData.Data;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
namespace Glamourer.Services;
@ -96,56 +94,4 @@ public sealed class ActorService : AsyncServiceWrapper<ActorManager>
: base(nameof(ActorService),
() => new ActorManager(pi, objects, clientState, framework, gameData, gui, idx => (short)penumbra.CutsceneParent(idx)))
{ }
}
public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationManager>
{
public CustomizationService(DalamudPluginInterface pi, DataManager gameData)
: base(nameof(CustomizationService), () => CustomizationManager.Create(pi, gameData))
{ }
/// <summary> In languages other than english the actual clan name may depend on gender. </summary>
public 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) => AwaitedService.GetName(CustomName.MidlanderM),
(Gender.Male, SubRace.Highlander) => AwaitedService.GetName(CustomName.HighlanderM),
(Gender.Male, SubRace.Wildwood) => AwaitedService.GetName(CustomName.WildwoodM),
(Gender.Male, SubRace.Duskwight) => AwaitedService.GetName(CustomName.DuskwightM),
(Gender.Male, SubRace.Plainsfolk) => AwaitedService.GetName(CustomName.PlainsfolkM),
(Gender.Male, SubRace.Dunesfolk) => AwaitedService.GetName(CustomName.DunesfolkM),
(Gender.Male, SubRace.SeekerOfTheSun) => AwaitedService.GetName(CustomName.SeekerOfTheSunM),
(Gender.Male, SubRace.KeeperOfTheMoon) => AwaitedService.GetName(CustomName.KeeperOfTheMoonM),
(Gender.Male, SubRace.Seawolf) => AwaitedService.GetName(CustomName.SeawolfM),
(Gender.Male, SubRace.Hellsguard) => AwaitedService.GetName(CustomName.HellsguardM),
(Gender.Male, SubRace.Raen) => AwaitedService.GetName(CustomName.RaenM),
(Gender.Male, SubRace.Xaela) => AwaitedService.GetName(CustomName.XaelaM),
(Gender.Male, SubRace.Helion) => AwaitedService.GetName(CustomName.HelionM),
(Gender.Male, SubRace.Lost) => AwaitedService.GetName(CustomName.LostM),
(Gender.Male, SubRace.Rava) => AwaitedService.GetName(CustomName.RavaM),
(Gender.Male, SubRace.Veena) => AwaitedService.GetName(CustomName.VeenaM),
(Gender.Female, SubRace.Midlander) => AwaitedService.GetName(CustomName.MidlanderF),
(Gender.Female, SubRace.Highlander) => AwaitedService.GetName(CustomName.HighlanderF),
(Gender.Female, SubRace.Wildwood) => AwaitedService.GetName(CustomName.WildwoodF),
(Gender.Female, SubRace.Duskwight) => AwaitedService.GetName(CustomName.DuskwightF),
(Gender.Female, SubRace.Plainsfolk) => AwaitedService.GetName(CustomName.PlainsfolkF),
(Gender.Female, SubRace.Dunesfolk) => AwaitedService.GetName(CustomName.DunesfolkF),
(Gender.Female, SubRace.SeekerOfTheSun) => AwaitedService.GetName(CustomName.SeekerOfTheSunF),
(Gender.Female, SubRace.KeeperOfTheMoon) => AwaitedService.GetName(CustomName.KeeperOfTheMoonF),
(Gender.Female, SubRace.Seawolf) => AwaitedService.GetName(CustomName.SeawolfF),
(Gender.Female, SubRace.Hellsguard) => AwaitedService.GetName(CustomName.HellsguardF),
(Gender.Female, SubRace.Raen) => AwaitedService.GetName(CustomName.RaenF),
(Gender.Female, SubRace.Xaela) => AwaitedService.GetName(CustomName.XaelaF),
(Gender.Female, SubRace.Helion) => AwaitedService.GetName(CustomName.HelionM),
(Gender.Female, SubRace.Lost) => AwaitedService.GetName(CustomName.LostM),
(Gender.Female, SubRace.Rava) => AwaitedService.GetName(CustomName.RavaF),
(Gender.Female, SubRace.Veena) => AwaitedService.GetName(CustomName.VeenaF),
_ => "Unknown",
};
}
}
}