Skip to content

Commit d2a8cec

Browse files
committed
Merge branch 'combining'
2 parents 9559bd7 + 795fa73 commit d2a8cec

File tree

13 files changed

+542
-48
lines changed

13 files changed

+542
-48
lines changed
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
using Dalamud.Interface.ImGuiNotification;
2+
using Newtonsoft.Json;
3+
using Newtonsoft.Json.Linq;
4+
using OtterGui;
5+
using OtterGui.Classes;
6+
using Penumbra.Api.Enums;
7+
using Penumbra.GameData.Data;
8+
using Penumbra.Meta.Manipulations;
9+
using Penumbra.Mods.Settings;
10+
using Penumbra.Mods.SubMods;
11+
using Penumbra.String.Classes;
12+
using Penumbra.UI.ModsTab.Groups;
13+
using Penumbra.Util;
14+
15+
namespace Penumbra.Mods.Groups;
16+
17+
/// <summary> Groups that allow all available options to be selected at once. </summary>
18+
public sealed class CombiningModGroup : IModGroup
19+
{
20+
public GroupType Type
21+
=> GroupType.Combining;
22+
23+
public GroupDrawBehaviour Behaviour
24+
=> GroupDrawBehaviour.MultiSelection;
25+
26+
public Mod Mod { get; }
27+
public string Name { get; set; } = "Group";
28+
public string Description { get; set; } = string.Empty;
29+
public string Image { get; set; } = string.Empty;
30+
public ModPriority Priority { get; set; }
31+
public int Page { get; set; }
32+
public Setting DefaultSettings { get; set; }
33+
public readonly List<CombiningSubMod> OptionData = [];
34+
public List<CombinedDataContainer> Data { get; private set; }
35+
36+
/// <summary> Groups that allow all available options to be selected at once. </summary>
37+
public CombiningModGroup(Mod mod)
38+
{
39+
Mod = mod;
40+
Data = [new CombinedDataContainer(this)];
41+
}
42+
43+
IReadOnlyList<IModOption> IModGroup.Options
44+
=> OptionData;
45+
46+
public IReadOnlyList<IModDataContainer> DataContainers
47+
=> Data;
48+
49+
public bool IsOption
50+
=> OptionData.Count > 0;
51+
52+
public FullPath? FindBestMatch(Utf8GamePath gamePath)
53+
{
54+
foreach (var path in Data.SelectWhere(o
55+
=> (o.Files.TryGetValue(gamePath, out var file) || o.FileSwaps.TryGetValue(gamePath, out file), file)))
56+
return path;
57+
58+
return null;
59+
}
60+
61+
public IModOption? AddOption(string name, string description = "")
62+
{
63+
var groupIdx = Mod.Groups.IndexOf(this);
64+
if (groupIdx < 0)
65+
return null;
66+
67+
var subMod = new CombiningSubMod(this)
68+
{
69+
Name = name,
70+
Description = description,
71+
};
72+
return OptionData.AddNewWithPowerSet(Data, subMod, () => new CombinedDataContainer(this), IModGroup.MaxCombiningOptions)
73+
? subMod
74+
: null;
75+
}
76+
77+
public static CombiningModGroup? Load(Mod mod, JObject json)
78+
{
79+
var ret = new CombiningModGroup(mod, true);
80+
if (!ModSaveGroup.ReadJsonBase(json, ret))
81+
return null;
82+
83+
var options = json["Options"];
84+
if (options != null)
85+
foreach (var child in options.Children())
86+
{
87+
if (ret.OptionData.Count == IModGroup.MaxCombiningOptions)
88+
{
89+
Penumbra.Messager.NotificationMessage(
90+
$"Combining Group {ret.Name} in {mod.Name} has more than {IModGroup.MaxCombiningOptions} options, ignoring excessive options.",
91+
NotificationType.Warning);
92+
break;
93+
}
94+
95+
var subMod = new CombiningSubMod(ret, child);
96+
ret.OptionData.Add(subMod);
97+
}
98+
99+
var requiredContainers = 1 << ret.OptionData.Count;
100+
var containers = json["Containers"];
101+
if (containers != null)
102+
foreach (var child in containers.Children())
103+
{
104+
if (requiredContainers <= ret.Data.Count)
105+
{
106+
Penumbra.Messager.NotificationMessage(
107+
$"Combining Group {ret.Name} in {mod.Name} has more data containers than it can support with {ret.OptionData.Count} options, ignoring excessive containers.",
108+
NotificationType.Warning);
109+
break;
110+
}
111+
112+
var container = new CombinedDataContainer(ret, child);
113+
ret.Data.Add(container);
114+
}
115+
116+
if (requiredContainers > ret.Data.Count)
117+
{
118+
Penumbra.Messager.NotificationMessage(
119+
$"Combining Group {ret.Name} in {mod.Name} has not enough data containers for its {ret.OptionData.Count} options, filling with empty containers.",
120+
NotificationType.Warning);
121+
ret.Data.EnsureCapacity(requiredContainers);
122+
ret.Data.AddRange(Enumerable.Repeat(0, requiredContainers - ret.Data.Count).Select(_ => new CombinedDataContainer(ret)));
123+
}
124+
125+
ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings);
126+
127+
return ret;
128+
}
129+
130+
public int GetIndex()
131+
=> ModGroup.GetIndex(this);
132+
133+
public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer)
134+
=> new CombiningModGroupEditDrawer(editDrawer, this);
135+
136+
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, MetaDictionary manipulations)
137+
=> Data[setting.AsIndex].AddDataTo(redirections, manipulations);
138+
139+
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, IIdentifiedObjectData?> changedItems)
140+
{
141+
foreach (var container in DataContainers)
142+
identifier.AddChangedItems(container, changedItems);
143+
}
144+
145+
public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null)
146+
{
147+
ModSaveGroup.WriteJsonBase(jWriter, this);
148+
jWriter.WritePropertyName("Options");
149+
jWriter.WriteStartArray();
150+
foreach (var option in OptionData)
151+
{
152+
jWriter.WriteStartObject();
153+
SubMod.WriteModOption(jWriter, option);
154+
jWriter.WriteEndObject();
155+
}
156+
157+
jWriter.WriteEndArray();
158+
159+
jWriter.WritePropertyName("Containers");
160+
jWriter.WriteStartArray();
161+
foreach (var container in Data)
162+
{
163+
jWriter.WriteStartObject();
164+
if (container.Name.Length > 0)
165+
{
166+
jWriter.WritePropertyName("Name");
167+
jWriter.WriteValue(container.Name);
168+
}
169+
170+
SubMod.WriteModContainer(jWriter, serializer, container, basePath ?? Mod.ModPath);
171+
jWriter.WriteEndObject();
172+
}
173+
174+
jWriter.WriteEndArray();
175+
}
176+
177+
public (int Redirections, int Swaps, int Manips) GetCounts()
178+
=> ModGroup.GetCountsBase(this);
179+
180+
public Setting FixSetting(Setting setting)
181+
=> new(Math.Min(setting.Value, (ulong)(Data.Count - 1)));
182+
183+
/// <summary> Create a group without a mod only for saving it in the creator. </summary>
184+
internal static CombiningModGroup WithoutMod(string name)
185+
=> new(null!)
186+
{
187+
Name = name,
188+
};
189+
190+
/// <summary> For loading when no empty container should be created. </summary>
191+
private CombiningModGroup(Mod mod, bool _)
192+
{
193+
Mod = mod;
194+
Data = [];
195+
}
196+
}

Penumbra/Mods/Groups/IModGroup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public enum GroupDrawBehaviour
2222

2323
public interface IModGroup
2424
{
25-
public const int MaxMultiOptions = 32;
25+
public const int MaxMultiOptions = 32;
26+
public const int MaxCombiningOptions = 8;
2627

2728
public Mod Mod { get; }
2829
public string Name { get; set; }
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using OtterGui;
2+
using OtterGui.Classes;
3+
using OtterGui.Services;
4+
using Penumbra.Mods.Groups;
5+
using Penumbra.Mods.Settings;
6+
using Penumbra.Mods.SubMods;
7+
using Penumbra.Services;
8+
9+
namespace Penumbra.Mods.Manager.OptionEditor;
10+
11+
public sealed class CombiningModGroupEditor(CommunicatorService communicator, SaveService saveService, Configuration config)
12+
: ModOptionEditor<CombiningModGroup, CombiningSubMod>(communicator, saveService, config), IService
13+
{
14+
protected override CombiningModGroup CreateGroup(Mod mod, string newName, ModPriority priority, SaveType saveType = SaveType.ImmediateSync)
15+
=> new(mod)
16+
{
17+
Name = newName,
18+
Priority = priority,
19+
};
20+
21+
protected override CombiningSubMod? CloneOption(CombiningModGroup group, IModOption option)
22+
=> throw new NotImplementedException();
23+
24+
protected override void RemoveOption(CombiningModGroup group, int optionIndex)
25+
{
26+
if (group.OptionData.RemoveWithPowerSet(group.Data, optionIndex))
27+
group.DefaultSettings.RemoveBit(optionIndex);
28+
}
29+
30+
protected override bool MoveOption(CombiningModGroup group, int optionIdxFrom, int optionIdxTo)
31+
{
32+
if (!group.OptionData.MoveWithPowerSet(group.Data, ref optionIdxFrom, ref optionIdxTo))
33+
return false;
34+
35+
group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
36+
return true;
37+
}
38+
39+
public void SetDisplayName(CombinedDataContainer container, string name, SaveType saveType = SaveType.Queue)
40+
{
41+
if (container.Name == name)
42+
return;
43+
44+
container.Name = name;
45+
SaveService.Save(saveType, new ModSaveGroup(container.Group, Config.ReplaceNonAsciiOnImport));
46+
Communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, container.Group.Mod, container.Group, null, null, -1);
47+
}
48+
}

Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class ModGroupEditor(
3737
SingleModGroupEditor singleEditor,
3838
MultiModGroupEditor multiEditor,
3939
ImcModGroupEditor imcEditor,
40+
CombiningModGroupEditor combiningEditor,
4041
CommunicatorService communicator,
4142
SaveService saveService,
4243
Configuration config) : IService
@@ -50,6 +51,9 @@ public MultiModGroupEditor MultiEditor
5051
public ImcModGroupEditor ImcEditor
5152
=> imcEditor;
5253

54+
public CombiningModGroupEditor CombiningEditor
55+
=> combiningEditor;
56+
5357
/// <summary> Change the settings stored as default options in a mod.</summary>
5458
public void ChangeModGroupDefaultOption(IModGroup group, Setting defaultOption)
5559
{
@@ -223,52 +227,60 @@ public void DeleteOption(IModOption option)
223227
case ImcSubMod i:
224228
ImcEditor.DeleteOption(i);
225229
return;
230+
case CombiningSubMod c:
231+
CombiningEditor.DeleteOption(c);
232+
return;
226233
}
227234
}
228235

229236
public IModOption? AddOption(IModGroup group, IModOption option)
230237
=> group switch
231238
{
232-
SingleModGroup s => SingleEditor.AddOption(s, option),
233-
MultiModGroup m => MultiEditor.AddOption(m, option),
234-
ImcModGroup i => ImcEditor.AddOption(i, option),
235-
_ => null,
239+
SingleModGroup s => SingleEditor.AddOption(s, option),
240+
MultiModGroup m => MultiEditor.AddOption(m, option),
241+
ImcModGroup i => ImcEditor.AddOption(i, option),
242+
CombiningModGroup c => CombiningEditor.AddOption(c, option),
243+
_ => null,
236244
};
237245

238246
public IModOption? AddOption(IModGroup group, string newName)
239247
=> group switch
240248
{
241-
SingleModGroup s => SingleEditor.AddOption(s, newName),
242-
MultiModGroup m => MultiEditor.AddOption(m, newName),
243-
ImcModGroup i => ImcEditor.AddOption(i, newName),
244-
_ => null,
249+
SingleModGroup s => SingleEditor.AddOption(s, newName),
250+
MultiModGroup m => MultiEditor.AddOption(m, newName),
251+
ImcModGroup i => ImcEditor.AddOption(i, newName),
252+
CombiningModGroup c => CombiningEditor.AddOption(c, newName),
253+
_ => null,
245254
};
246255

247256
public IModGroup? AddModGroup(Mod mod, GroupType type, string newName, SaveType saveType = SaveType.ImmediateSync)
248257
=> type switch
249258
{
250-
GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType),
251-
GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType),
252-
GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, default, saveType),
253-
_ => null,
259+
GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType),
260+
GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType),
261+
GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, default, saveType),
262+
GroupType.Combining => CombiningEditor.AddModGroup(mod, newName, saveType),
263+
_ => null,
254264
};
255265

256266
public (IModGroup?, int, bool) FindOrAddModGroup(Mod mod, GroupType type, string name, SaveType saveType = SaveType.ImmediateSync)
257267
=> type switch
258268
{
259-
GroupType.Single => SingleEditor.FindOrAddModGroup(mod, name, saveType),
260-
GroupType.Multi => MultiEditor.FindOrAddModGroup(mod, name, saveType),
261-
GroupType.Imc => ImcEditor.FindOrAddModGroup(mod, name, saveType),
262-
_ => (null, -1, false),
269+
GroupType.Single => SingleEditor.FindOrAddModGroup(mod, name, saveType),
270+
GroupType.Multi => MultiEditor.FindOrAddModGroup(mod, name, saveType),
271+
GroupType.Imc => ImcEditor.FindOrAddModGroup(mod, name, saveType),
272+
GroupType.Combining => CombiningEditor.FindOrAddModGroup(mod, name, saveType),
273+
_ => (null, -1, false),
263274
};
264275

265276
public (IModOption?, int, bool) FindOrAddOption(IModGroup group, string name, SaveType saveType = SaveType.ImmediateSync)
266277
=> group switch
267278
{
268-
SingleModGroup s => SingleEditor.FindOrAddOption(s, name, saveType),
269-
MultiModGroup m => MultiEditor.FindOrAddOption(m, name, saveType),
270-
ImcModGroup i => ImcEditor.FindOrAddOption(i, name, saveType),
271-
_ => (null, -1, false),
279+
SingleModGroup s => SingleEditor.FindOrAddOption(s, name, saveType),
280+
MultiModGroup m => MultiEditor.FindOrAddOption(m, name, saveType),
281+
ImcModGroup i => ImcEditor.FindOrAddOption(i, name, saveType),
282+
CombiningModGroup c => CombiningEditor.FindOrAddOption(c, name, saveType),
283+
_ => (null, -1, false),
272284
};
273285

274286
public void MoveOption(IModOption option, int toIdx)
@@ -284,6 +296,9 @@ public void MoveOption(IModOption option, int toIdx)
284296
case ImcSubMod i:
285297
ImcEditor.MoveOption(i, toIdx);
286298
return;
299+
case CombiningSubMod c:
300+
CombiningEditor.MoveOption(c, toIdx);
301+
return;
287302
}
288303
}
289304
}

0 commit comments

Comments
 (0)