Add option to apply entire weapon.

This commit is contained in:
Ottermandias 2024-01-24 15:42:24 +01:00
parent 5fd4a83aa4
commit b6549899e8
5 changed files with 92 additions and 35 deletions

View file

@ -41,6 +41,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool UseFloatForColors { get; set; } = true; public bool UseFloatForColors { get; set; } = true;
public bool UseRgbForColors { get; set; } = true; public bool UseRgbForColors { get; set; } = true;
public bool ShowColorConfig { get; set; } = true; public bool ShowColorConfig { get; set; } = true;
public bool ChangeEntireItem { get; set; } = false;
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;

View file

@ -4,7 +4,6 @@ using Glamourer.Events;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
@ -17,6 +16,7 @@ namespace Glamourer.Designs;
public class DesignManager public class DesignManager
{ {
private readonly CustomizeService _customizations; private readonly CustomizeService _customizations;
private readonly Configuration _config;
private readonly ItemManager _items; private readonly ItemManager _items;
private readonly HumanModelList _humans; private readonly HumanModelList _humans;
private readonly SaveService _saveService; private readonly SaveService _saveService;
@ -28,14 +28,15 @@ public class DesignManager
=> _designs; => _designs;
public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations,
DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader) DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration config)
{ {
_designs = storage; _designs = storage;
_saveService = saveService; _config = config;
_items = items; _saveService = saveService;
_customizations = customizations; _items = items;
_event = @event; _customizations = customizations;
_humans = humans; _event = @event;
_humans = humans;
LoadDesigns(designLinkLoader); LoadDesigns(designLinkLoader);
CreateDesignFolder(saveService); CreateDesignFolder(saveService);
@ -382,26 +383,18 @@ public class DesignManager
switch (slot) switch (slot)
{ {
case EquipSlot.MainHand: case EquipSlot.MainHand:
var newOff = currentOff;
if (!_items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item)) if (!_items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item))
return; return;
if (item.Type != currentMain.Type) if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets))
{
var defaultOffhand = _items.GetDefaultOffhand(item);
if (!_items.IsOffhandValid(item, defaultOffhand.ItemId, out newOff))
return;
}
if (!(design.GetDesignDataRef().SetItem(EquipSlot.MainHand, item)
| design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff)))
return; return;
design.LastEdit = DateTimeOffset.UtcNow; design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design); _saveService.QueueSave(design);
Glamourer.Log.Debug( Glamourer.Log.Debug(
$"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId})."); $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId}).");
_event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff)); _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff, newGauntlets));
return; return;
case EquipSlot.OffHand: case EquipSlot.OffHand:
@ -415,7 +408,7 @@ public class DesignManager
_saveService.QueueSave(design); _saveService.QueueSave(design);
Glamourer.Log.Debug( Glamourer.Log.Debug(
$"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId})."); $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId}).");
_event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item)); _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item, (EquipItem?)null));
return; return;
default: return; default: return;
} }
@ -503,15 +496,7 @@ public class DesignManager
/// <summary> Change the bool value of one of the meta flags. </summary> /// <summary> Change the bool value of one of the meta flags. </summary>
public void ChangeMeta(Design design, MetaIndex metaIndex, bool value) public void ChangeMeta(Design design, MetaIndex metaIndex, bool value)
{ {
var change = metaIndex switch if (!design.GetDesignDataRef().SetMeta(metaIndex, value))
{
MetaIndex.Wetness => design.GetDesignDataRef().SetIsWet(value),
MetaIndex.HatState => design.GetDesignDataRef().SetHatVisible(value),
MetaIndex.VisorState => design.GetDesignDataRef().SetVisor(value),
MetaIndex.WeaponState => design.GetDesignDataRef().SetWeaponVisible(value),
_ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null),
};
if (!change)
return; return;
design.LastEdit = DateTimeOffset.UtcNow; design.LastEdit = DateTimeOffset.UtcNow;
@ -753,4 +738,46 @@ public class DesignManager
return (actualName, path); return (actualName, path);
} }
/// <summary> Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. </summary>
private bool ChangeMainhandPeriphery(Design design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, out EquipItem? newGauntlets)
{
newOff = null;
newGauntlets = null;
if (newMain.Type != currentMain.Type)
{
var defaultOffhand = _items.GetDefaultOffhand(newMain);
if (!_items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o))
return false;
newOff = o;
}
else if (_config.ChangeEntireItem)
{
var defaultOffhand = _items.GetDefaultOffhand(newMain);
if (_items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o))
newOff = o;
if (newMain.Type is FullEquipType.Fists && _items.ItemData.Tertiary.TryGetValue(newMain.ItemId, out var g))
newGauntlets = g;
}
if (!design.GetDesignDataRef().SetItem(EquipSlot.MainHand, newMain))
return false;
if (newOff.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff.Value))
{
design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain);
return false;
}
if (newGauntlets.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.Hands, newGauntlets.Value))
{
design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain);
design.GetDesignDataRef().SetItem(EquipSlot.OffHand, currentOff);
return false;
}
return true;
}
} }

View file

@ -59,7 +59,7 @@ public sealed class DesignChanged()
/// <summary> An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary> /// <summary> An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary>
Equip, Equip,
/// <summary> An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. </summary> /// <summary> An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand, the new offhand (if any) and the new gauntlets (if any). [(EquipItem, EquipItem, EquipItem, EquipItem?, EquipItem?)]. </summary>
Weapon, Weapon,
/// <summary> An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary> /// <summary> An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>

View file

@ -68,6 +68,9 @@ public class SettingsTab(
if (!ImGui.CollapsingHeader("Glamourer Behavior")) if (!ImGui.CollapsingHeader("Glamourer Behavior"))
return; return;
Checkbox("Always Apply Entire Weapon for Mainhand",
"When manually applying a mainhand item, will also apply a corresponding offhand and potentially gauntlets for certain fist weapons.",
config.ChangeEntireItem, v => config.ChangeEntireItem = v);
Checkbox("Use Replacement Gear for Gear Unavailable to Your Race or Gender", Checkbox("Use Replacement Gear for Gear Unavailable to Your Race or Gender",
"Use different gender- and race-appropriate models as a substitute when detecting certain items not available for a characters current gender and race.", "Use different gender- and race-appropriate models as a substitute when detecting certain items not available for a characters current gender and race.",
config.UseRestrictedGearProtection, v => config.UseRestrictedGearProtection = v); config.UseRestrictedGearProtection, v => config.UseRestrictedGearProtection = v);

View file

@ -20,7 +20,8 @@ public class StateManager(
StateEditor _editor, StateEditor _editor,
HumanModelList _humans, HumanModelList _humans,
ICondition _condition, ICondition _condition,
IClientState _clientState) IClientState _clientState,
Configuration _config)
: IReadOnlyDictionary<ActorIdentifier, ActorState> : IReadOnlyDictionary<ActorIdentifier, ActorState>
{ {
private readonly Dictionary<ActorIdentifier, ActorState> _states = []; private readonly Dictionary<ActorIdentifier, ActorState> _states = [];
@ -260,6 +261,10 @@ public class StateManager(
? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc) ? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc)
: _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc, : _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc,
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, source, key);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, actors, (old, item, slot)); _event.Invoke(type, source, state, actors, (old, item, slot));
@ -276,6 +281,10 @@ public class StateManager(
? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc) ? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc)
: _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc, : _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc,
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, source, key);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, actors, (old, item, slot)); _event.Invoke(type, source, state, actors, (old, item, slot));
@ -290,6 +299,7 @@ public class StateManager(
return; return;
var actors = _applier.ChangeStain(state, slot, source is StateSource.Manual or StateSource.Ipc); var actors = _applier.ChangeStain(state, slot, source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot)); _event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot));
@ -430,9 +440,9 @@ public class StateManager(
if (state.ModelData.IsHuman) if (state.ModelData.IsHuman)
{ {
_applier.ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); _applier.ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible());
_applier.ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); _applier.ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible());
_applier.ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); _applier.ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled());
_applier.ChangeCrests(actors, state.ModelData.CrestVisibility); _applier.ChangeCrests(actors, state.ModelData.CrestVisibility);
_applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); _applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
} }
@ -503,7 +513,7 @@ public class StateManager(
foreach (var index in Enum.GetValues<CustomizeIndex>().Where(i => state.Sources[i] is StateSource.Fixed)) foreach (var index in Enum.GetValues<CustomizeIndex>().Where(i => state.Sources[i] is StateSource.Fixed))
{ {
state.Sources[index] = StateSource.Game; state.Sources[index] = StateSource.Game;
state.ModelData.Customize[index] = state.BaseData.Customize[index]; state.ModelData.Customize[index] = state.BaseData.Customize[index];
} }
@ -537,7 +547,7 @@ public class StateManager(
{ {
case StateSource.Fixed: case StateSource.Fixed:
case StateSource.Manual when !respectManualPalettes: case StateSource.Manual when !respectManualPalettes:
state.Sources[flag] = StateSource.Game; state.Sources[flag] = StateSource.Game;
state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag]; state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag];
break; break;
} }
@ -579,4 +589,20 @@ public class StateManager(
public void DeleteState(ActorIdentifier identifier) public void DeleteState(ActorIdentifier identifier)
=> _states.Remove(identifier); => _states.Remove(identifier);
/// <summary> Apply offhand item and potentially gauntlets if configured. </summary>
private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StateSource source, uint key = 0)
{
if (!_config.ChangeEntireItem || source is not StateSource.Manual)
return;
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
var offhand = newMainhand != null ? _items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand);
if (offhand.Valid)
ChangeEquip(state, EquipSlot.OffHand, offhand, state.ModelData.Stain(EquipSlot.OffHand), source, key);
if (mh is { Type: FullEquipType.Fists } && _items.ItemData.Tertiary.TryGetValue(mh.ItemId, out var gauntlets))
ChangeEquip(state, EquipSlot.Hands, newMainhand != null ? gauntlets : state.ModelData.Item(EquipSlot.Hands),
state.ModelData.Stain(EquipSlot.Hands), source, key);
}
} }