mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-18 21:47:44 +01:00
Extricate bonus slots somewhat.
This commit is contained in:
parent
7caf6cc08a
commit
7a602d6ec5
8 changed files with 86 additions and 37 deletions
25
Glamourer/Events/BonusSlotUpdating.cs
Normal file
25
Glamourer/Events/BonusSlotUpdating.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a model flags a bonus slot for an update.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the model with a flagged slot. </item>
|
||||||
|
/// <item>Parameter is the bonus slot changed. </item>
|
||||||
|
/// <item>Parameter is the model values to change the bonus piece to. </item>
|
||||||
|
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BonusSlotUpdating()
|
||||||
|
: EventWrapperRef34<Model, BonusEquipFlag, CharacterArmor, ulong, BonusSlotUpdating.Priority>(nameof(BonusSlotUpdating))
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="State.StateListener.OnBonusSlotUpdating"/>
|
||||||
|
StateListener = 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,12 +14,12 @@ namespace Glamourer.Events;
|
||||||
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
|
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SlotUpdating()
|
public sealed class EquipSlotUpdating()
|
||||||
: EventWrapperRef34<Model, EquipSlot, CharacterArmor, ulong, SlotUpdating.Priority>(nameof(SlotUpdating))
|
: EventWrapperRef34<Model, EquipSlot, CharacterArmor, ulong, EquipSlotUpdating.Priority>(nameof(EquipSlotUpdating))
|
||||||
{
|
{
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
/// <seealso cref="State.StateListener.OnSlotUpdating"/>
|
/// <seealso cref="State.StateListener.OnEquipSlotUpdating"/>
|
||||||
StateListener = 0,
|
StateListener = 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +90,7 @@ public unsafe class ModelEvaluationPanel(
|
||||||
: "No CharacterBase");
|
: "No CharacterBase");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawParameters(Actor actor, Model model)
|
private static void DrawParameters(Actor actor, Model model)
|
||||||
{
|
{
|
||||||
if (!model.IsHuman)
|
if (!model.IsHuman)
|
||||||
return;
|
return;
|
||||||
|
|
@ -140,13 +140,13 @@ public unsafe class ModelEvaluationPanel(
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ImGui.SmallButton("Hide"))
|
if (ImGui.SmallButton("Hide"))
|
||||||
_updateSlotService.UpdateSlot(model, EquipSlot.Head, CharacterArmor.Empty);
|
_updateSlotService.UpdateEquipSlot(model, EquipSlot.Head, CharacterArmor.Empty);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.SmallButton("Show"))
|
if (ImGui.SmallButton("Show"))
|
||||||
_updateSlotService.UpdateSlot(model, EquipSlot.Head, actor.GetArmor(EquipSlot.Head));
|
_updateSlotService.UpdateEquipSlot(model, EquipSlot.Head, actor.GetArmor(EquipSlot.Head));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.SmallButton("Toggle"))
|
if (ImGui.SmallButton("Toggle"))
|
||||||
_updateSlotService.UpdateSlot(model, EquipSlot.Head,
|
_updateSlotService.UpdateEquipSlot(model, EquipSlot.Head,
|
||||||
model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty);
|
model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,31 +223,32 @@ public unsafe class ModelEvaluationPanel(
|
||||||
_updateSlotService.UpdateStain(model, slot, new StainIds(5, 7));
|
_updateSlotService.UpdateStain(model, slot, new StainIds(5, 7));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.SmallButton("Reset"))
|
if (ImGui.SmallButton("Reset"))
|
||||||
_updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot));
|
_updateSlotService.UpdateEquipSlot(model, slot, actor.GetArmor(slot));
|
||||||
}
|
}
|
||||||
|
|
||||||
using (ImRaii.PushId((int)EquipSlot.FaceWear))
|
foreach (var slot in BonusSlotExtensions.AllFlags)
|
||||||
{
|
{
|
||||||
ImGuiUtil.DrawTableColumn(EquipSlot.FaceWear.ToName());
|
using var id2 = ImRaii.PushId((int)slot.ToModelIndex());
|
||||||
|
ImGuiUtil.DrawTableColumn(slot.ToName());
|
||||||
if (!actor.IsCharacter)
|
if (!actor.IsCharacter)
|
||||||
{
|
{
|
||||||
ImGuiUtil.DrawTableColumn("No Character");
|
ImGuiUtil.DrawTableColumn("No Character");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var glassesId = actor.AsCharacter->DrawData.GlassesIds[(int)EquipSlot.FaceWear.ToBonusIndex()];
|
var glassesId = actor.GetBonusSlot(slot);
|
||||||
if (_glasses.TryGetValue(glassesId, out var glasses))
|
if (_glasses.TryGetValue(glassesId, out var glasses))
|
||||||
ImGuiUtil.DrawTableColumn($"{glasses.Id.Id},{glasses.Variant.Id} ({glassesId})");
|
ImGuiUtil.DrawTableColumn($"{glasses.Id.Id},{glasses.Variant.Id} ({glassesId})");
|
||||||
else
|
else
|
||||||
ImGuiUtil.DrawTableColumn($"{glassesId}");
|
ImGuiUtil.DrawTableColumn($"{glassesId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiUtil.DrawTableColumn(model.IsHuman ? model.GetArmor(EquipSlot.FaceWear).ToString() : "No Human");
|
ImGuiUtil.DrawTableColumn(model.IsHuman ? model.GetBonus(slot).ToString() : "No Human");
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (ImUtf8.SmallButton("Change Piece"u8))
|
if (ImUtf8.SmallButton("Change Piece"u8))
|
||||||
{
|
{
|
||||||
var data = model.GetArmor(EquipSlot.FaceWear);
|
var data = model.GetBonus(slot);
|
||||||
_updateSlotService.UpdateSlot(model, EquipSlot.FaceWear, data with { Variant = (Variant)((data.Variant.Id + 1) % 12) });
|
_updateSlotService.UpdateBonusSlot(model, slot, data with { Variant = (Variant)((data.Variant.Id + 1) % 12) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Interop;
|
using Penumbra.GameData.Interop;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
@ -11,11 +12,16 @@ namespace Glamourer.Interop;
|
||||||
|
|
||||||
public unsafe class UpdateSlotService : IDisposable
|
public unsafe class UpdateSlotService : IDisposable
|
||||||
{
|
{
|
||||||
public readonly SlotUpdating SlotUpdatingEvent;
|
public readonly EquipSlotUpdating EquipSlotUpdatingEvent;
|
||||||
|
public readonly BonusSlotUpdating BonusSlotUpdatingEvent;
|
||||||
|
private readonly DictGlasses _glasses;
|
||||||
|
|
||||||
public UpdateSlotService(SlotUpdating slotUpdating, IGameInteropProvider interop)
|
public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, IGameInteropProvider interop,
|
||||||
|
DictGlasses glasses)
|
||||||
{
|
{
|
||||||
SlotUpdatingEvent = slotUpdating;
|
EquipSlotUpdatingEvent = equipSlotUpdating;
|
||||||
|
BonusSlotUpdatingEvent = bonusSlotUpdating;
|
||||||
|
_glasses = glasses;
|
||||||
interop.InitializeFromAttributes(this);
|
interop.InitializeFromAttributes(this);
|
||||||
_flagSlotForUpdateHook.Enable();
|
_flagSlotForUpdateHook.Enable();
|
||||||
_flagBonusSlotForUpdateHook.Enable();
|
_flagBonusSlotForUpdateHook.Enable();
|
||||||
|
|
@ -27,20 +33,37 @@ public unsafe class UpdateSlotService : IDisposable
|
||||||
_flagBonusSlotForUpdateHook.Dispose();
|
_flagBonusSlotForUpdateHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
|
public void UpdateEquipSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
|
||||||
{
|
{
|
||||||
if (!drawObject.IsCharacterBase)
|
if (!drawObject.IsCharacterBase)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var bonusSlot = slot.ToBonusIndex();
|
FlagSlotForUpdateInterop(drawObject, slot, data);
|
||||||
if (bonusSlot == uint.MaxValue)
|
}
|
||||||
FlagSlotForUpdateInterop(drawObject, slot, data);
|
|
||||||
else
|
public void UpdateBonusSlot(Model drawObject, BonusEquipFlag slot, CharacterArmor data)
|
||||||
_flagBonusSlotForUpdateHook.Original(drawObject.Address, bonusSlot, &data);
|
{
|
||||||
|
if (!drawObject.IsCharacterBase)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var index = slot.ToIndex();
|
||||||
|
if (index == uint.MaxValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_flagBonusSlotForUpdateHook.Original(drawObject.Address, index, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateGlasses(Model drawObject, GlassesId id)
|
||||||
|
{
|
||||||
|
if (!_glasses.TryGetValue(id, out var glasses))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var armor = new CharacterArmor(glasses.Id, glasses.Variant, StainIds.None);
|
||||||
|
_flagBonusSlotForUpdateHook.Original(drawObject.Address, BonusEquipFlag.Glasses.ToIndex(), &armor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains)
|
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains)
|
||||||
=> UpdateSlot(drawObject, slot, armor.With(stains));
|
=> UpdateEquipSlot(drawObject, slot, armor.With(stains));
|
||||||
|
|
||||||
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor)
|
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor)
|
||||||
=> UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stains);
|
=> UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stains);
|
||||||
|
|
@ -60,7 +83,7 @@ public unsafe class UpdateSlotService : IDisposable
|
||||||
{
|
{
|
||||||
var slot = slotIdx.ToEquipSlot();
|
var slot = slotIdx.ToEquipSlot();
|
||||||
var returnValue = ulong.MaxValue;
|
var returnValue = ulong.MaxValue;
|
||||||
SlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue);
|
EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue);
|
||||||
Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X}).");
|
Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X}).");
|
||||||
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
|
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +92,7 @@ public unsafe class UpdateSlotService : IDisposable
|
||||||
{
|
{
|
||||||
var slot = slotIdx.ToBonusSlot();
|
var slot = slotIdx.ToBonusSlot();
|
||||||
var returnValue = ulong.MaxValue;
|
var returnValue = ulong.MaxValue;
|
||||||
SlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue);
|
BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue);
|
||||||
Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X}).");
|
Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X}).");
|
||||||
return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
|
return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ public static class StaticServiceManager
|
||||||
|
|
||||||
private static ServiceManager AddEvents(this ServiceManager services)
|
private static ServiceManager AddEvents(this ServiceManager services)
|
||||||
=> services.AddSingleton<VisorStateChanged>()
|
=> services.AddSingleton<VisorStateChanged>()
|
||||||
.AddSingleton<SlotUpdating>()
|
.AddSingleton<EquipSlotUpdating>()
|
||||||
.AddSingleton<DesignChanged>()
|
.AddSingleton<DesignChanged>()
|
||||||
.AddSingleton<AutomationChanged>()
|
.AddSingleton<AutomationChanged>()
|
||||||
.AddSingleton<StateChanged>()
|
.AddSingleton<StateChanged>()
|
||||||
|
|
|
||||||
|
|
@ -105,11 +105,11 @@ public class StateApplier(
|
||||||
{
|
{
|
||||||
var customize = mdl.GetCustomize();
|
var customize = mdl.GetCustomize();
|
||||||
var (_, resolvedItem) = _items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender);
|
var (_, resolvedItem) = _items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender);
|
||||||
_updateSlot.UpdateSlot(actor.Model, slot, resolvedItem);
|
_updateSlot.UpdateEquipSlot(actor.Model, slot, resolvedItem);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_updateSlot.UpdateSlot(actor.Model, slot, armor);
|
_updateSlot.UpdateEquipSlot(actor.Model, slot, armor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ public class StateListener : IDisposable
|
||||||
private readonly ItemManager _items;
|
private readonly ItemManager _items;
|
||||||
private readonly CustomizeService _customizations;
|
private readonly CustomizeService _customizations;
|
||||||
private readonly PenumbraService _penumbra;
|
private readonly PenumbraService _penumbra;
|
||||||
private readonly SlotUpdating _slotUpdating;
|
private readonly EquipSlotUpdating _equipSlotUpdating;
|
||||||
private readonly WeaponLoading _weaponLoading;
|
private readonly WeaponLoading _weaponLoading;
|
||||||
private readonly HeadGearVisibilityChanged _headGearVisibility;
|
private readonly HeadGearVisibilityChanged _headGearVisibility;
|
||||||
private readonly VisorStateChanged _visorState;
|
private readonly VisorStateChanged _visorState;
|
||||||
|
|
@ -52,7 +52,7 @@ public class StateListener : IDisposable
|
||||||
private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty;
|
private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty;
|
||||||
|
|
||||||
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config,
|
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config,
|
||||||
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
|
EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
|
||||||
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans,
|
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans,
|
||||||
StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose,
|
StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose,
|
||||||
ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, CrestService crestService)
|
ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, CrestService crestService)
|
||||||
|
|
@ -62,7 +62,7 @@ public class StateListener : IDisposable
|
||||||
_penumbra = penumbra;
|
_penumbra = penumbra;
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
_config = config;
|
_config = config;
|
||||||
_slotUpdating = slotUpdating;
|
_equipSlotUpdating = equipSlotUpdating;
|
||||||
_weaponLoading = weaponLoading;
|
_weaponLoading = weaponLoading;
|
||||||
_visorState = visorState;
|
_visorState = visorState;
|
||||||
_weaponVisibility = weaponVisibility;
|
_weaponVisibility = weaponVisibility;
|
||||||
|
|
@ -202,7 +202,7 @@ public class StateListener : IDisposable
|
||||||
/// A draw model loads a new equipment piece.
|
/// A draw model loads a new equipment piece.
|
||||||
/// Update base data, apply or update model data, and protect against restricted gear.
|
/// Update base data, apply or update model data, and protect against restricted gear.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnSlotUpdating(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue)
|
private void OnEquipSlotUpdating(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue)
|
||||||
{
|
{
|
||||||
var actor = _penumbra.GameObjectFromDrawObject(model);
|
var actor = _penumbra.GameObjectFromDrawObject(model);
|
||||||
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
|
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
|
||||||
|
|
@ -699,7 +699,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
|
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
|
||||||
_penumbra.CreatedCharacterBase += OnCreatedCharacterBase;
|
_penumbra.CreatedCharacterBase += OnCreatedCharacterBase;
|
||||||
_slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener);
|
_equipSlotUpdating.Subscribe(OnEquipSlotUpdating, EquipSlotUpdating.Priority.StateListener);
|
||||||
_movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener);
|
_movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener);
|
||||||
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
|
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
|
||||||
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
|
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
|
||||||
|
|
@ -715,7 +715,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
|
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
|
||||||
_penumbra.CreatedCharacterBase -= OnCreatedCharacterBase;
|
_penumbra.CreatedCharacterBase -= OnCreatedCharacterBase;
|
||||||
_slotUpdating.Unsubscribe(OnSlotUpdating);
|
_equipSlotUpdating.Unsubscribe(OnEquipSlotUpdating);
|
||||||
_movedEquipment.Unsubscribe(OnMovedEquipment);
|
_movedEquipment.Unsubscribe(OnMovedEquipment);
|
||||||
_weaponLoading.Unsubscribe(OnWeaponLoading);
|
_weaponLoading.Unsubscribe(OnWeaponLoading);
|
||||||
_visorState.Unsubscribe(OnVisorChange);
|
_visorState.Unsubscribe(OnVisorChange);
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 8ec296d1f8113ae2ba509527749cd3e8f54debbf
|
Subproject commit d83303ccc3ec5d7237f5da621e9c2433ad28f9e1
|
||||||
Loading…
Add table
Add a link
Reference in a new issue