Add preliminary festival handling

This commit is contained in:
Ottermandias 2023-09-19 20:38:45 +02:00
parent 301c089d86
commit dee1860dee
8 changed files with 275 additions and 10 deletions

View file

@ -27,6 +27,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool HideApplyCheckmarks { get; set; } = false;
public bool SmallEquip { get; set; } = false;
public bool UnlockedItemMode { get; set; } = false;
public byte DisableFestivals { get; set; } = 1;
public bool EnableGameContextMenu { get; set; } = true;
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);

View file

@ -0,0 +1,73 @@
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Windowing;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
namespace Glamourer.Gui;
public class GenericPopupWindow : Window
{
private Configuration _config;
public bool OpenFestivalPopup { get; internal set; } = false;
public GenericPopupWindow(Configuration config)
: base("Glamourer Popups",
ImGuiWindowFlags.NoBringToFrontOnFocus
| ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoInputs
| ImGuiWindowFlags.NoSavedSettings
| ImGuiWindowFlags.NoBackground
| ImGuiWindowFlags.NoMove
| ImGuiWindowFlags.NoNav
| ImGuiWindowFlags.NoTitleBar, true)
{
_config = config;
IsOpen = true;
}
public override void Draw()
{
if (OpenFestivalPopup)
{
ImGui.OpenPopup("FestivalPopup");
OpenFestivalPopup = false;
}
DrawFestivalPopup();
}
private void DrawFestivalPopup()
{
var viewportSize = ImGui.GetWindowViewport().Size;
ImGui.SetNextWindowSize(new Vector2(viewportSize.X / 5, viewportSize.Y / 7));
ImGui.SetNextWindowPos(viewportSize / 2, ImGuiCond.Always, new Vector2(0.5f));
using var popup = ImRaii.Popup("FestivalPopup", ImGuiWindowFlags.Modal);
if (!popup)
return;
ImGuiUtil.TextWrapped(
"Glamourer has some festival-specific behaviour that is turned on by default. You can always turn this behaviour on or off in the general settings, and choose your current preference now.");
var buttonWidth = new Vector2(150 * ImGuiHelpers.GlobalScale, 0);
var yPos = ImGui.GetWindowHeight() - 2 * ImGui.GetFrameHeight();
var xPos = (ImGui.GetWindowWidth() - ImGui.GetStyle().ItemSpacing.X) / 2 - buttonWidth.X;
ImGui.SetCursorPos(new Vector2(xPos, yPos));
if (ImGui.Button("Let's Check It Out!", buttonWidth))
{
_config.DisableFestivals = 0;
_config.Save();
ImGui.CloseCurrentPopup();
}
ImGui.SameLine();
if (ImGui.Button("Not Right Now.", buttonWidth))
{
_config.DisableFestivals = 2;
_config.Save();
ImGui.CloseCurrentPopup();
}
}
}

View file

@ -11,12 +11,13 @@ public class GlamourerWindowSystem : IDisposable
private readonly MainWindow _ui;
private readonly PenumbraChangedItemTooltip _penumbraTooltip;
public GlamourerWindowSystem(UiBuilder uiBuilder, MainWindow ui, PenumbraChangedItemTooltip penumbraTooltip)
public GlamourerWindowSystem(UiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups, PenumbraChangedItemTooltip penumbraTooltip)
{
_uiBuilder = uiBuilder;
_ui = ui;
_penumbraTooltip = penumbraTooltip;
_windowSystem.AddWindow(ui);
_windowSystem.AddWindow(popups);
_uiBuilder.Draw += _windowSystem.Draw;
_uiBuilder.OpenConfigUi += _ui.Toggle;
}
@ -26,4 +27,4 @@ public class GlamourerWindowSystem : IDisposable
_uiBuilder.Draw -= _windowSystem.Draw;
_uiBuilder.OpenConfigUi -= _ui.Toggle;
}
}
}

View file

@ -66,19 +66,21 @@ public unsafe class DebugTab : ITab
private readonly PenumbraChangedItemTooltip _penumbraTooltip;
private readonly StateManager _state;
private readonly FunModule _funModule;
private int _gameObjectIndex;
public bool IsVisible
=> _config.DebugMode;
public DebugTab(ChangeCustomizeService changeCustomizeService, VisorService visorService, UpdateSlotService updateSlotService, WeaponService weaponService, PenumbraService penumbra,
public DebugTab(ChangeCustomizeService changeCustomizeService, VisorService visorService, UpdateSlotService updateSlotService,
WeaponService weaponService, PenumbraService penumbra,
ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager,
DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config,
PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface,
AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks,
ItemUnlockManager itemUnlocks, DesignConverter designConverter, DatFileService datFileService, InventoryService inventoryService,
HumanModelList humans)
HumanModelList humans, FunModule funModule)
{
_changeCustomizeService = changeCustomizeService;
_visorService = visorService;
@ -106,6 +108,7 @@ public unsafe class DebugTab : ITab
_datFileService = datFileService;
_inventoryService = inventoryService;
_humans = humans;
_funModule = funModule;
}
public ReadOnlySpan<byte> Label
@ -125,6 +128,7 @@ public unsafe class DebugTab : ITab
DrawAutoDesigns();
DrawInventory();
DrawUnlocks();
DrawFun();
DrawIpc();
}
@ -1569,6 +1573,35 @@ public unsafe class DebugTab : ITab
#endregion
#region Fun
private void DrawFun()
{
if (!ImGui.CollapsingHeader("Fun Module"))
return;
ImGui.TextUnformatted($"Current Festival: {_funModule.CurrentFestival}");
ImGui.TextUnformatted($"Festivals Enabled: {_config.DisableFestivals switch { 1 => "Undecided", 0 => "Enabled", _ => "Disabled" }}");
ImGui.TextUnformatted($"Popup Open: {ImGui.IsPopupOpen("FestivalPopup", ImGuiPopupFlags.AnyPopup)}");
if (ImGui.Button("Force Christmas"))
_funModule.ForceFestival(FunModule.FestivalType.Christmas);
if (ImGui.Button("Force Halloween"))
_funModule.ForceFestival(FunModule.FestivalType.Halloween);
if (ImGui.Button("Force April First"))
_funModule.ForceFestival(FunModule.FestivalType.AprilFirst);
if (ImGui.Button("Force None"))
_funModule.ForceFestival(FunModule.FestivalType.None);
if (ImGui.Button("Revert"))
_funModule.ResetFestival();
if (ImGui.Button("Reset Popup"))
{
_config.DisableFestivals = 1;
_config.Save();
}
}
#endregion
#region IPC
private string _gameObjectName = string.Empty;
@ -1600,7 +1633,8 @@ public unsafe class DebugTab : ITab
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromCharacter);
ImGui.TableNextColumn();
base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface).Invoke(_objectManager.Objects[_gameObjectIndex] as Character);
base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface)
.Invoke(_objectManager.Objects[_gameObjectIndex] as Character);
if (base64 != null)
ImGuiUtil.CopyOnClickSelectable(base64);
else
@ -1624,7 +1658,8 @@ public unsafe class DebugTab : ITab
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##AllCharacter"))
GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface).Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character);
GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface)
.Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment);
ImGui.TableNextColumn();

View file

@ -53,6 +53,9 @@ public class SettingsTab : ITab
Checkbox("Unlocked Item Mode",
"Enable this if you want automatically applied designs to only consider items and customizations you have actually unlocked once, and skip those you have not.",
_config.UnlockedItemMode, v => _config.UnlockedItemMode = v);
Checkbox("Enable Festival Easter-Eggs",
"Glamourer may do some fun things on specific dates. Disable this if you do not want your experience disrupted by this.",
_config.DisableFestivals == 0, v => _config.DisableFestivals = v ? (byte)0 : (byte)2);
Checkbox("Auto-Reload Gear",
"Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection.",
_config.AutoRedrawEquipOnChanges, _autoRedraw.SetState);

View file

@ -120,6 +120,7 @@ public static class ServiceManager
.AddSingleton<ActorSelector>()
.AddSingleton<ActorPanel>()
.AddSingleton<MainWindow>()
.AddSingleton<GenericPopupWindow>()
.AddSingleton<GlamourerWindowSystem>()
.AddSingleton<CustomizationDrawer>()
.AddSingleton<EquipmentDrawer>()

View file

@ -0,0 +1,95 @@
using System;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
internal class FunEquipSet
{
public static FunEquipSet? GetSet(FunModule.FestivalType type)
{
return type switch
{
FunModule.FestivalType.Halloween => Halloween,
FunModule.FestivalType.Christmas => Christmas,
FunModule.FestivalType.AprilFirst => AprilFirst,
_ => null,
};
}
public readonly record struct Group(CharacterArmor Head, CharacterArmor Body, CharacterArmor Hands, CharacterArmor Legs,
CharacterArmor Feet, StainId[]? Stains = null)
{
public Group(ushort headS, byte headV, ushort bodyS, byte bodyV, ushort handsS, byte handsV, ushort legsS, byte legsV, ushort feetS,
byte feetV, StainId[]? stains = null)
: this(new CharacterArmor(headS, headV, 0), new CharacterArmor(bodyS, bodyV, 0), new CharacterArmor(handsS, handsV, 0),
new CharacterArmor(legsS, legsV, 0), new CharacterArmor(feetS, feetV, 0), stains)
{ }
}
private readonly Group[] _groups;
public void Apply(StainId[] allStains, Random rng, Span<CharacterArmor> armor)
{
var groupIdx = rng.Next(0, _groups.Length);
var group = _groups[groupIdx];
var stains = group.Stains ?? allStains;
var stainIdx = rng.Next(0, stains.Length);
var stain = stains[stainIdx];
Glamourer.Log.Verbose($"Chose group {groupIdx} and stain {stainIdx} for festival costume.");
if (group.Head.Set != 0)
armor[0] = group.Head.With(stain);
if (group.Body.Set != 0)
armor[1] = group.Body.With(stain);
if (group.Hands.Set != 0)
armor[2] = group.Hands.With(stain);
if (group.Legs.Set != 0)
armor[3] = group.Legs.With(stain);
if (group.Feet.Set != 0)
armor[4] = group.Feet.With(stain);
}
public static readonly FunEquipSet Christmas = new
(
new Group(6170, 1, 6170, 1, 6170, 1, 6170, 1, 6170, 1), // Unorthodox Saint
new Group(6006, 1, 6170, 1, 6170, 1, 6170, 1, 6170, 1), // Unorthodox Saint
new Group(6007, 1, 6170, 1, 6170, 1, 6170, 1, 6170, 1), // Unorthodox Saint
new Group(0058, 1, 0058, 1, 6005, 1, 0000, 0, 6005, 1), // Reindeer
new Group(6005, 1, 0058, 1, 6005, 1, 0000, 0, 6005, 1), // Reindeer
new Group(0231, 1, 0231, 1, 0279, 1, 0231, 1, 0231, 1), // Starlight
new Group(0231, 1, 6030, 1, 0279, 1, 0231, 1, 0231, 1), // Starlight
new Group(0053, 1, 0053, 1, 0279, 1, 0279, 1, 0053, 1), // Sweet Dream
new Group(0136, 1, 0136, 1, 0136, 1, 0000, 0, 0000, 0) // Snowman
);
public static readonly FunEquipSet Halloween = new
(
new Group(0316, 1, 0316, 1, 0316, 1, 0279, 1, 0316, 1), // Witch
new Group(6047, 1, 6047, 1, 6047, 1, 6047, 1, 6047, 1), // Werewolf
new Group(6148, 1, 6148, 1, 6148, 1, 6148, 1, 6148, 1), // Wake Doctor
new Group(6117, 1, 6117, 1, 6117, 1, 6117, 1, 6117, 1), // Clown
new Group(0000, 0, 0137, 1, 0000, 0, 0000, 0, 0000, 0), // Howling Spirit
new Group(0000, 0, 0137, 2, 0000, 0, 0000, 0, 0000, 0), // Wailing Spirit
new Group(0232, 1, 0232, 1, 0279, 1, 0232, 1, 0232, 1), // Eerie Attire
new Group(0232, 1, 6036, 1, 0279, 1, 0232, 1, 0232, 1) // Vampire
);
public static readonly FunEquipSet AprilFirst = new
(
new Group(6133, 1, 6133, 1, 0000, 0, 0000, 0, 0000, 0), // Gaja
new Group(6121, 1, 6121, 1, 0000, 0, 0000, 0, 0000, 0), // Chicken
new Group(6103, 1, 6103, 1, 0000, 0, 0000, 0, 0000, 0), // Rabbit
new Group(6103, 1, 6103, 2, 0000, 0, 0000, 0, 0000, 0), // Rabbit
new Group(6089, 1, 6089, 1, 0000, 0, 0000, 0, 0000, 0), // Toad
new Group(0241, 1, 0046, 1, 0000, 0, 0279, 1, 6169, 3), // Chocobo
new Group(0046, 1, 0046, 1, 0000, 0, 0279, 1, 6169, 3), // Chocobo
new Group(0058, 1, 0058, 1, 0000, 0, 0000, 0, 0000, 0), // Reindeer
new Group(0136, 1, 0136, 1, 0136, 1, 0000, 0, 0000, 0), // Snowman
new Group(0159, 1, 0000, 0, 0000, 0, 0000, 0, 0000, 0), // Slime Crown
new Group(6117, 1, 6117, 1, 6117, 1, 6117, 1, 6117, 1), // Clown
new Group(6169, 3, 6169, 3, 0279, 1, 6169, 3, 6169, 3), // Chocobo Pajama
new Group(6169, 2, 6169, 2, 0279, 2, 6169, 2, 6169, 2) // Cactuar Pajama
);
private FunEquipSet(params Group[] groups)
=> _groups = groups;
}

View file

@ -2,31 +2,80 @@
using System.Linq;
using Dalamud.Game.ClientState.Objects.Enums;
using Glamourer.Customization;
using Glamourer.Gui;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
namespace Glamourer.State;
public unsafe class FunModule
public unsafe class FunModule : IDisposable
{
public enum FestivalType
{
None,
Halloween,
Christmas,
AprilFirst,
}
private readonly ItemManager _items;
private readonly CustomizationService _customizations;
private readonly Configuration _config;
private readonly CodeService _codes;
private readonly Random _rng;
private readonly GenericPopupWindow _popupWindow;
private readonly StainId[] _stains;
public FunModule(CodeService codes, CustomizationService customizations, ItemManager items)
public FestivalType CurrentFestival { get; private set; } = FestivalType.None;
private FunEquipSet? _festivalSet;
private void OnDayChange(int day, int month, int _)
{
CurrentFestival = (day, month) switch
{
(1, 4) => FestivalType.AprilFirst,
(24, 12) => FestivalType.Christmas,
(25, 12) => FestivalType.Christmas,
(26, 12) => FestivalType.Christmas,
(31, 10) => FestivalType.Halloween,
(01, 11) => FestivalType.Halloween,
_ => FestivalType.None,
};
_festivalSet = FunEquipSet.GetSet(CurrentFestival);
_popupWindow.OpenFestivalPopup = _festivalSet != null && _config.DisableFestivals == 1;
}
internal void ForceFestival(FestivalType type)
{
CurrentFestival = type;
_festivalSet = FunEquipSet.GetSet(CurrentFestival);
_popupWindow.OpenFestivalPopup = _festivalSet != null && _config.DisableFestivals == 1;
}
internal void ResetFestival()
=> OnDayChange(DateTime.UtcNow.Day, DateTime.UtcNow.Month, DateTime.UtcNow.Year);
public FunModule(CodeService codes, CustomizationService customizations, ItemManager items, Configuration config,
GenericPopupWindow popupWindow)
{
_codes = codes;
_customizations = customizations;
_items = items;
_config = config;
_popupWindow = popupWindow;
_rng = new Random();
_stains = _items.Stains.Keys.Prepend((StainId)0).ToArray();
ResetFestival();
DayChangeTracker.DayChanged += OnDayChange;
}
public void Dispose()
=> DayChangeTracker.DayChanged -= OnDayChange;
public void ApplyFun(Actor actor, ref CharacterArmor armor, EquipSlot slot)
{
if (actor.AsObject->ObjectKind is not (byte)ObjectKind.Player || !actor.IsCharacter)
@ -47,8 +96,15 @@ public unsafe class FunModule
if (actor.AsCharacter->CharacterData.ModelCharaId != 0)
return;
ApplyEmperor(armor);
ApplyClown(armor);
if (_config.DisableFestivals == 0 && _festivalSet != null)
{
_festivalSet.Apply(_stains, _rng, armor);
}
else
{
ApplyEmperor(armor);
ApplyClown(armor);
}
ApplyOops(ref customize);
ApplyIndividual(ref customize);