mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-20 06:27:43 +01:00
..
This commit is contained in:
parent
27f151c55a
commit
d10cb3137f
14 changed files with 1366 additions and 373 deletions
211
Glamourer/Services/CustomizationService.cs
Normal file
211
Glamourer/Services/CustomizationService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ public class FilenameService
|
|||
DesignDirectory = Path.Combine(ConfigDirectory, "designs");
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<FileInfo> Designs()
|
||||
{
|
||||
if (!Directory.Exists(DesignDirectory))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue