diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index dbf74cc..3c683a5 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -78,8 +78,8 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList }; // Event NPCs have a reference to NpcEquip but also contain the appearance in their own row. - // Prefer the NpcEquip reference if it is set, otherwise use the own. - if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) + // Prefer the NpcEquip reference if it is set and the own does not appear to be set, otherwise use the own. + if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip && row is { ModelBody: 0, ModelLegs: 0 }) ApplyNpcEquip(ref ret, equip); else ApplyNpcEquip(ref ret, row); diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 61339e1..5ab0d8f 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -30,13 +30,19 @@ public class CodeService Elephants = 0x020000, Crown = 0x040000, Dolphins = 0x080000, + Face = 0x100000, + Manderville = 0x200000, + Smiles = 0x400000, } public static readonly CodeFlag AllHintCodes = Enum.GetValues().Where(f => GetData(f).Display).Aggregate((CodeFlag)0, (f1, f2) => f1 | f2); - public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins; - public const CodeFlag GearCodes = CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins; + public const CodeFlag DyeCodes = + CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins; + + public const CodeFlag GearCodes = + CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins; public const CodeFlag RaceCodes = CodeFlag.OopsHyur | CodeFlag.OopsElezen @@ -46,6 +52,8 @@ public class CodeService | CodeFlag.OopsAuRa | CodeFlag.OopsHrothgar; + public const CodeFlag FullCodes = CodeFlag.Face | CodeFlag.Manderville | CodeFlag.Smiles; + public const CodeFlag SizeCodes = CodeFlag.Dwarf | CodeFlag.Giant; private CodeFlag _enabled; @@ -161,26 +169,29 @@ public class CodeService private static CodeFlag GetMutuallyExclusive(CodeFlag flag) => flag switch { - CodeFlag.Clown => DyeCodes & ~CodeFlag.Clown, - CodeFlag.Emperor => GearCodes & ~CodeFlag.Emperor, - CodeFlag.Individual => 0, - CodeFlag.Dwarf => SizeCodes & ~CodeFlag.Dwarf, - CodeFlag.Giant => SizeCodes & ~CodeFlag.Giant, - CodeFlag.OopsHyur => RaceCodes & ~CodeFlag.OopsHyur, - CodeFlag.OopsElezen => RaceCodes & ~CodeFlag.OopsElezen, - CodeFlag.OopsLalafell => RaceCodes & ~CodeFlag.OopsLalafell, - CodeFlag.OopsMiqote => RaceCodes & ~CodeFlag.OopsMiqote, - CodeFlag.OopsRoegadyn => RaceCodes & ~CodeFlag.OopsRoegadyn, - CodeFlag.OopsAuRa => RaceCodes & ~CodeFlag.OopsAuRa, - CodeFlag.OopsHrothgar => RaceCodes & ~CodeFlag.OopsHrothgar, - CodeFlag.OopsViera => RaceCodes & ~CodeFlag.OopsViera, + CodeFlag.Clown => (FullCodes | DyeCodes) & ~CodeFlag.Clown, + CodeFlag.Emperor => (FullCodes | GearCodes) & ~CodeFlag.Emperor, + CodeFlag.Individual => FullCodes, + CodeFlag.Dwarf => (FullCodes | SizeCodes) & ~CodeFlag.Dwarf, + CodeFlag.Giant => (FullCodes | SizeCodes) & ~CodeFlag.Giant, + CodeFlag.OopsHyur => (FullCodes | RaceCodes) & ~CodeFlag.OopsHyur, + CodeFlag.OopsElezen => (FullCodes | RaceCodes) & ~CodeFlag.OopsElezen, + CodeFlag.OopsLalafell => (FullCodes | RaceCodes) & ~CodeFlag.OopsLalafell, + CodeFlag.OopsMiqote => (FullCodes | RaceCodes) & ~CodeFlag.OopsMiqote, + CodeFlag.OopsRoegadyn => (FullCodes | RaceCodes) & ~CodeFlag.OopsRoegadyn, + CodeFlag.OopsAuRa => (FullCodes | RaceCodes) & ~CodeFlag.OopsAuRa, + CodeFlag.OopsHrothgar => (FullCodes | RaceCodes) & ~CodeFlag.OopsHrothgar, + CodeFlag.OopsViera => (FullCodes | RaceCodes) & ~CodeFlag.OopsViera, CodeFlag.Artisan => 0, - CodeFlag.SixtyThree => 0, + CodeFlag.SixtyThree => FullCodes, CodeFlag.Shirts => 0, - CodeFlag.World => (DyeCodes | GearCodes) & ~CodeFlag.World, - CodeFlag.Elephants => (DyeCodes | GearCodes) & ~CodeFlag.Elephants, - CodeFlag.Crown => 0, - CodeFlag.Dolphins => (DyeCodes | GearCodes) & ~CodeFlag.Dolphins, + CodeFlag.World => (FullCodes | DyeCodes | GearCodes) & ~CodeFlag.World, + CodeFlag.Elephants => (FullCodes | DyeCodes | GearCodes) & ~CodeFlag.Elephants, + CodeFlag.Crown => FullCodes, + CodeFlag.Dolphins => (FullCodes | DyeCodes | GearCodes) & ~CodeFlag.Dolphins, + CodeFlag.Face => (FullCodes | RaceCodes | SizeCodes | GearCodes | DyeCodes | CodeFlag.Crown | CodeFlag.SixtyThree) & ~CodeFlag.Face, + CodeFlag.Manderville => (FullCodes | RaceCodes | SizeCodes | GearCodes | DyeCodes | CodeFlag.Crown | CodeFlag.SixtyThree) & ~CodeFlag.Manderville, + CodeFlag.Smiles => (FullCodes | RaceCodes | SizeCodes | GearCodes | DyeCodes | CodeFlag.Crown | CodeFlag.SixtyThree) & ~CodeFlag.Smiles, _ => 0, }; @@ -207,6 +218,9 @@ public class CodeService CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ], CodeFlag.Crown => [ 0x43, 0x8E, 0x34, 0x56, 0x24, 0xC9, 0xC6, 0xDE, 0x2A, 0x68, 0x3A, 0x5D, 0xF5, 0x8E, 0xCB, 0xEF, 0x0D, 0x4D, 0x5B, 0xDC, 0x23, 0xF9, 0xF9, 0xBD, 0xD9, 0x60, 0xAD, 0x53, 0xC5, 0xA0, 0x33, 0xC4 ], CodeFlag.Dolphins => [ 0x64, 0xC6, 0x2E, 0x7C, 0x22, 0x3A, 0x42, 0xF5, 0xC3, 0x93, 0x4F, 0x70, 0x1F, 0xFD, 0xFA, 0x3C, 0x98, 0xD2, 0x7C, 0xD8, 0x88, 0xA7, 0x3D, 0x1D, 0x0D, 0xD6, 0x70, 0x15, 0x28, 0x2E, 0x79, 0xE7 ], + CodeFlag.Face => [ 0xCA, 0x97, 0x81, 0x12, 0xCA, 0x1B, 0xBD, 0xCA, 0xFA, 0xC2, 0x31, 0xB3, 0x9A, 0x23, 0xDC, 0x4D, 0xA7, 0x86, 0xEF, 0xF8, 0x14, 0x7C, 0x4E, 0x72, 0xB9, 0x80, 0x77, 0x85, 0xAF, 0xEE, 0x48, 0xBB ], + CodeFlag.Manderville => [ 0x3E, 0x23, 0xE8, 0x16, 0x00, 0x39, 0x59, 0x4A, 0x33, 0x89, 0x4F, 0x65, 0x64, 0xE1, 0xB1, 0x34, 0x8B, 0xBD, 0x7A, 0x00, 0x88, 0xD4, 0x2C, 0x4A, 0xCB, 0x73, 0xEE, 0xAE, 0xD5, 0x9C, 0x00, 0x9D ], + CodeFlag.Smiles => [ 0x2E, 0x7D, 0x2C, 0x03, 0xA9, 0x50, 0x7A, 0xE2, 0x65, 0xEC, 0xF5, 0xB5, 0x35, 0x68, 0x85, 0xA5, 0x33, 0x93, 0xA2, 0x02, 0x9D, 0x24, 0x13, 0x94, 0x99, 0x72, 0x65, 0xA1, 0xA2, 0x5A, 0xEF, 0xC6 ], _ => [], }; @@ -233,6 +247,9 @@ public class CodeService CodeFlag.Crown => (true, 1, ".", "A famous Shakespearean line.", "Sets every player with a mentor symbol enabled to the clown's hat."), CodeFlag.Dolphins => (true, 5, ",", "The farewell of the second smartest species on Earth.", "Sets every player to a Namazu hat with different costume bodies."), CodeFlag.Artisan => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."), + CodeFlag.Face => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."), + CodeFlag.Manderville => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."), + CodeFlag.Smiles => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."), _ => (false, 0, string.Empty, string.Empty, string.Empty), }; } diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index a9ce8e2..f07ff93 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; +using Glamourer.GameData; using Glamourer.Gui; using Glamourer.Services; using ImGuiNET; @@ -35,6 +36,7 @@ public unsafe class FunModule : IDisposable private readonly DesignConverter _designConverter; private readonly DesignManager _designManager; private readonly ObjectManager _objects; + private readonly NpcCustomizeSet _npcs; private readonly StainId[] _stains; public FestivalType CurrentFestival { get; private set; } = FestivalType.None; @@ -68,7 +70,7 @@ public unsafe class FunModule : IDisposable public FunModule(CodeService codes, CustomizeService customizations, ItemManager items, Configuration config, GenericPopupWindow popupWindow, StateManager stateManager, ObjectManager objects, DesignConverter designConverter, - DesignManager designManager) + DesignManager designManager, NpcCustomizeSet npcs) { _codes = codes; _customizations = customizations; @@ -79,6 +81,7 @@ public unsafe class FunModule : IDisposable _objects = objects; _designConverter = designConverter; _designManager = designManager; + _npcs = npcs; _rng = new Random(); _stains = _items.Stains.Keys.Prepend((StainId)0).ToArray(); ResetFestival(); @@ -102,6 +105,15 @@ public unsafe class FunModule : IDisposable return; } + switch (_codes.Masked(CodeService.FullCodes)) + { + case CodeService.CodeFlag.Face: + case CodeService.CodeFlag.Manderville: + case CodeService.CodeFlag.Smiles: + KeepOldArmor(actor, slot, ref armor); + return; + } + if (_codes.Enabled(CodeService.CodeFlag.Crown) && actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor && slot.IsEquipment()) @@ -130,11 +142,124 @@ public unsafe class FunModule : IDisposable } } + private sealed class PrioritizedList : List<(T Item, int Priority)> + { + private int _cumulative; + + public PrioritizedList(params (T Item, int Priority)[] list) + { + if (list.Length == 0) + return; + + AddRange(list.Where(p => p.Priority > 0).OrderByDescending(p => p.Priority).Select(p => (p.Item, _cumulative += p.Priority))); + } + + public T GetRandom(Random rng) + { + var val = rng.Next(0, _cumulative); + foreach (var (item, priority) in this) + { + if (val < priority) + return item; + } + + // Should never happen. + return this[^1].Item1; + } + } + + private static readonly PrioritizedList MandervilleMale = new + ( + (1008264, 30), // Hildi + (1008731, 10), // Hildi, slightly damaged + (1011668, 3), // Zombi + (1016617, 5), // Hildi, heavily damaged + (1042518, 1), // Hildi of Light + (1006339, 2), // Godbert, naked + (1008734, 10), // Godbert, shorts + (1015921, 5), // Godbert, ripped + (1041606, 5), // Godbert, only shorts + (1041605, 5), // Godbert, summer + (1024501, 30), // Godbert, fully clothed + (1045184, 3), // Godbrand + (1044749, 1) // Brandihild + ); + + private static readonly PrioritizedList MandervilleFemale = new + ( + (1025669, 5), // Hildi, Geisha + (1025670, 2), // Hildi, makeup, black + (1042477, 2), // Hildi, makeup, white + (1016798, 20), // Julyan, Winter + (1011707, 30), // Julyan + (1005714, 20), // Nashu + (1025668, 5), // Nashu, Kimono + (1025674, 5), // Nashu, fancy + (1042486, 30), // Nashu, inspector + (1017263, 3), // Gigi + (1017263, 1) // Gigi, buff + ); + + private static readonly PrioritizedList Smile = new + ( + (1046504, 75), // Normal + (1046501, 20), // Hat + (1050613, 4), // Armor + (1047625, 1) // Elephant + ); + + private static readonly PrioritizedList FaceMale = new + ( + (1016136, 35), // Gerolt + (1032667, 2), // Gerolt, Suit + (1030519, 35), // Grenoldt + (1030519, 20), // Grenoldt, Short + (1046262, 2), // Grenoldt, Suit + (1048084, 15) // Genolt + ); + + private static readonly PrioritizedList FaceFemale = new + ( + (1013713, 10), // Rowena, Togi + (1018496, 30), // Rowena, Poncho + (1032668, 2), // Rowena, Gown + (1042857, 10), // Rowena, Hannish + (1046255, 10), // Mowen, Miner + (1046263, 2), // Mowen, Gown + (1027544, 30), // Mowen, Bustle + (1049088, 15) // Rhodina + ); + + private bool ApplyFullCode(Actor actor, Span armor, ref CustomizeArray customize) + { + var id = _codes.Masked(CodeService.FullCodes) switch + { + CodeService.CodeFlag.Face when customize.Gender is Gender.Female && actor.Index != 0 => FaceFemale.GetRandom(_rng), + CodeService.CodeFlag.Face when actor.Index != 0 => FaceFemale.GetRandom(_rng), + CodeService.CodeFlag.Smiles => Smile.GetRandom(_rng), + CodeService.CodeFlag.Manderville when customize.Gender is Gender.Female => MandervilleFemale.GetRandom(_rng), + CodeService.CodeFlag.Manderville => MandervilleMale.GetRandom(_rng), + _ => (NpcId)0, + }; + + if (id.Id == 0 || !_npcs.FindFirst(n => n.Id == id, out var npc)) + return false; + + customize = npc.Customize; + var idx = 0; + foreach (ref var a in armor) + a = npc.Equip[idx++]; + return true; + } + public void ApplyFunOnLoad(Actor actor, Span armor, ref CustomizeArray customize) { if (!ValidFunTarget(actor)) return; + if (ApplyFullCode(actor, armor, ref customize)) + return; + // First set the race, if any. SetRace(ref customize); // Now apply the gender.