Skip to content

Commit 3f6b8dd

Browse files
authored
Add SpawnLocker (code provided by BitMagnet) (#354)
* Add SpawnLocker (code provided by BitMagnet) * Apply repo code style; Refactoring * Relax package requirements * Add to readme * Fix namespace * Update packages * Fix wrong guid * Minor fixes
1 parent 4bafb25 commit 3f6b8dd

File tree

12 files changed

+444
-0
lines changed

12 files changed

+444
-0
lines changed

KK_Plugins.sln

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HS2.TimelineFlowControl", "
587587
EndProject
588588
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AI.TimelineFlowControl", "src\TimelineFlowControl.AI\AI.TimelineFlowControl.csproj", "{2AB45B21-1B6A-40E7-B172-F9A32DD5392D}"
589589
EndProject
590+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SpawnLocker", "SpawnLocker", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
591+
EndProject
592+
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Core.SpawnLocker", "src\SpawnLocker.Core\Core.SpawnLocker.shproj", "{A545658E-D416-40B6-90C6-BFD776147D3E}"
593+
EndProject
594+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KK.SpawnLocker", "src\SpawnLocker.KK\KK.SpawnLocker.csproj", "{1CC189B7-2C5F-460B-C140-D14CFBD39CB8}"
595+
EndProject
596+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KKS.SpawnLocker", "src\SpawnLocker.KKS\KKS.SpawnLocker.csproj", "{5A6D7A38-C1FF-7369-EECA-532AD766D270}"
597+
EndProject
590598
Global
591599
GlobalSection(SolutionConfigurationPlatforms) = preSolution
592600
Debug|Any CPU = Debug|Any CPU
@@ -1309,6 +1317,14 @@ Global
13091317
{2AB45B21-1B6A-40E7-B172-F9A32DD5392D}.Debug|Any CPU.Build.0 = Debug|Any CPU
13101318
{2AB45B21-1B6A-40E7-B172-F9A32DD5392D}.Release|Any CPU.ActiveCfg = Release|Any CPU
13111319
{2AB45B21-1B6A-40E7-B172-F9A32DD5392D}.Release|Any CPU.Build.0 = Release|Any CPU
1320+
{1CC189B7-2C5F-460B-C140-D14CFBD39CB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1321+
{1CC189B7-2C5F-460B-C140-D14CFBD39CB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
1322+
{1CC189B7-2C5F-460B-C140-D14CFBD39CB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
1323+
{1CC189B7-2C5F-460B-C140-D14CFBD39CB8}.Release|Any CPU.Build.0 = Release|Any CPU
1324+
{5A6D7A38-C1FF-7369-EECA-532AD766D270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1325+
{5A6D7A38-C1FF-7369-EECA-532AD766D270}.Debug|Any CPU.Build.0 = Debug|Any CPU
1326+
{5A6D7A38-C1FF-7369-EECA-532AD766D270}.Release|Any CPU.ActiveCfg = Release|Any CPU
1327+
{5A6D7A38-C1FF-7369-EECA-532AD766D270}.Release|Any CPU.Build.0 = Release|Any CPU
13121328
EndGlobalSection
13131329
GlobalSection(SolutionProperties) = preSolution
13141330
HideSolutionNode = FALSE
@@ -1546,6 +1562,9 @@ Global
15461562
{9AC3E3FF-99A8-41F8-BA5D-6C305C08A7A5} = {12FF93A3-5883-44E0-84D3-D65042A6E1E4}
15471563
{80D061E6-9CBC-4086-BE30-AFFF166C85AF} = {12FF93A3-5883-44E0-84D3-D65042A6E1E4}
15481564
{2AB45B21-1B6A-40E7-B172-F9A32DD5392D} = {12FF93A3-5883-44E0-84D3-D65042A6E1E4}
1565+
{A545658E-D416-40B6-90C6-BFD776147D3E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
1566+
{1CC189B7-2C5F-460B-C140-D14CFBD39CB8} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
1567+
{5A6D7A38-C1FF-7369-EECA-532AD766D270} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
15491568
EndGlobalSection
15501569
GlobalSection(ExtensibilityGlobals) = postSolution
15511570
SolutionGuid = {D0F79985-4CB7-46CB-BEC2-FF89C476ED20}
@@ -1598,6 +1617,8 @@ Global
15981617
src\Subtitles.Core\Core.Subtitles.projitems*{19cfff4d-8bda-4c45-9904-28552b32a200}*SharedItemsImports = 5
15991618
src\Shared\Shared.projitems*{1c3fd7d8-e631-490a-bcdc-fb04e524d151}*SharedItemsImports = 5
16001619
src\StudioImageEmbed.Core\Core.StudioImageEmbed.projitems*{1c3fd7d8-e631-490a-bcdc-fb04e524d151}*SharedItemsImports = 5
1620+
src\Shared\Shared.projitems*{1cc189b7-2c5f-460b-c140-d14cfbd39cb8}*SharedItemsImports = 5
1621+
src\SpawnLocker.Core\SpawnLocker.Core.projitems*{1cc189b7-2c5f-460b-c140-d14cfbd39cb8}*SharedItemsImports = 5
16011622
src\Shared\Shared.projitems*{1d8216cf-b9f0-4463-b120-02c068cc70b6}*SharedItemsImports = 5
16021623
src\StudioWindowResize.Core\StudioWindowResize.Core.projitems*{1d8216cf-b9f0-4463-b120-02c068cc70b6}*SharedItemsImports = 5
16031624
src\Shared\Shared.projitems*{1e2af057-e10c-4363-902a-db8b843081a8}*SharedItemsImports = 5
@@ -1715,6 +1736,8 @@ Global
17151736
src\StudioWindowResize.Core\StudioWindowResize.Core.projitems*{589ea7e6-340d-4273-8f58-1fd7d6a7d594}*SharedItemsImports = 5
17161737
src\AccessoryQuickRemove.Core\AccessoryQuickRemove.Core.projitems*{589fb078-fcf6-4096-a4f3-5fa362e5d0bf}*SharedItemsImports = 5
17171738
src\Shared\Shared.projitems*{589fb078-fcf6-4096-a4f3-5fa362e5d0bf}*SharedItemsImports = 5
1739+
src\Shared\Shared.projitems*{5a6d7a38-c1ff-7369-eeca-532ad766d270}*SharedItemsImports = 5
1740+
src\SpawnLocker.Core\SpawnLocker.Core.projitems*{5a6d7a38-c1ff-7369-eeca-532ad766d270}*SharedItemsImports = 5
17181741
src\MaterialEditor.Core\Core.MaterialEditor.projitems*{5a8fdb27-ffca-4a77-ab0f-afdef9a034ed}*SharedItemsImports = 13
17191742
src\Pushup.Core\Pushup.Core.projitems*{5b9d6ca7-4a75-4299-8280-9fd16b6f4c89}*SharedItemsImports = 5
17201743
src\Shared\Shared.projitems*{5b9d6ca7-4a75-4299-8280-9fd16b6f4c89}*SharedItemsImports = 5
@@ -1848,6 +1871,7 @@ Global
18481871
src\Shared\Shared.projitems*{a0e2000f-196f-4ead-b571-20a4446b7824}*SharedItemsImports = 5
18491872
src\StudioImageEmbed.Core\Core.StudioImageEmbed.projitems*{a18c34c6-1220-487a-8448-fc1a8251fc57}*SharedItemsImports = 13
18501873
src\ShaderSwapper.Core\ShaderSwapper.Core.projitems*{a27f5e6b-5e24-4609-a634-1a704ebbf1d9}*SharedItemsImports = 13
1874+
src\SpawnLocker.Core\SpawnLocker.Core.projitems*{a545658e-d416-40b6-90c6-bfd776147d3e}*SharedItemsImports = 13
18511875
src\Profile.Core\Profile.Core.projitems*{a566dc0b-c265-447f-86e0-806e3c8abe46}*SharedItemsImports = 5
18521876
src\Shared\Shared.projitems*{a566dc0b-c265-447f-86e0-806e3c8abe46}*SharedItemsImports = 5
18531877
src\FreeHRandom.Core\FreeHRandom.Core.projitems*{a59cf955-a07b-4ef1-a9b5-169e0ce750da}*SharedItemsImports = 5

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ Custom rules for swapping shaders can be provided in xml files. Check the "Mappi
294294
```
295295

296296
</details>
297+
298+
#### SpawnLocker
299+
300+
Allows you to select certain characters that you want to always appear in free roam (when there are more characters than the maximum to be loaded at one time).
301+
To use, middle click on a character portrait in the roster screen. You should see a message appear and the character portrait to be marked.
297302

298303
## Experimental and obsolete plugins
299304
Generally you don't want to install these unless you have a very good reason to do so.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using KK_Plugins;
2+
using System.Reflection;
3+
using static SpawnLocker.SpawnLockMain;
4+
5+
[assembly: AssemblyTitle(PluginName)]
6+
[assembly: AssemblyProduct(PluginName)]
7+
[assembly: AssemblyDescription(GUID + " for " + Constants.GameName)]
8+
[assembly: AssemblyVersion(Version)]
9+
[assembly: AssemblyFileVersion(Version)]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup Label="Globals">
4+
<ProjectGuid>{A545658E-D416-40B6-90C6-BFD776147D3E}</ProjectGuid>
5+
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
6+
</PropertyGroup>
7+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
8+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
9+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
10+
<PropertyGroup />
11+
<Import Project="SpawnLocker.Core.projitems" Label="Shared" />
12+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
13+
</Project>

src/SpawnLocker.Core/Hooks.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using HarmonyLib;
2+
using KKAPI.Utilities;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Reflection.Emit;
7+
8+
namespace SpawnLocker
9+
{
10+
internal static class Hooks
11+
{
12+
public static void Apply()
13+
{
14+
var harmony = Harmony.CreateAndPatchAll(typeof(Hooks), SpawnLockMain.GUID);
15+
16+
var transpiler = new HarmonyMethod(typeof(Hooks), nameof(Hooks.NPCLoadAllTranspile));
17+
18+
foreach (var targetMethod in typeof(ActionScene).GetMethods(AccessTools.all).Where(x => x.Name == nameof(ActionScene.NPCLoadAll)))
19+
{
20+
SpawnLockMain.Logger.LogDebug("Patching: " + targetMethod.FullDescription());
21+
harmony.PatchMoveNext(targetMethod, transpiler: transpiler);
22+
}
23+
}
24+
25+
private static IEnumerable<CodeInstruction> NPCLoadAllTranspile(IEnumerable<CodeInstruction> instructions)
26+
{
27+
var targetMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.Take)).MakeGenericMethod(typeof(SaveData.Heroine));
28+
var newTakeMethod = AccessTools.Method(typeof(SpawnLockMain), nameof(SpawnLockMain._SpawnLockTake));
29+
30+
foreach (var instruction in instructions)
31+
{
32+
if (instruction.opcode != OpCodes.Call || instruction.operand as MethodInfo != targetMethod)
33+
{
34+
yield return instruction;
35+
}
36+
else
37+
{
38+
yield return new CodeInstruction(OpCodes.Call, newTakeMethod);
39+
}
40+
}
41+
}
42+
43+
[HarmonyPostfix]
44+
[HarmonyPatch(typeof(ActionGame.PreviewClassData), nameof(ActionGame.PreviewClassData.Set))]
45+
private static void PreviewClassDataSetPostfix(ActionGame.PreviewClassData __instance, SaveData.CharaData charaData)
46+
{
47+
_UpdateStatus(__instance);
48+
}
49+
50+
[HarmonyPostfix]
51+
[HarmonyPatch(typeof(ActionGame.PreviewClassData), nameof(ActionGame.PreviewClassData.Clear))]
52+
private static void PreviewClassDataClearPostfix(ActionGame.PreviewClassData __instance)
53+
{
54+
_UpdateStatus(__instance);
55+
}
56+
57+
static void _UpdateStatus(ActionGame.PreviewClassData __instance)
58+
{
59+
var observer = __instance.button.GetComponent<ClickObserver>();
60+
61+
if (observer != null)
62+
{
63+
observer.UpdateStatus();
64+
}
65+
}
66+
67+
[HarmonyPostfix]
68+
#if KK
69+
[HarmonyPatch(typeof(ActionGame.PreviewClassData), MethodType.Constructor, new System.Type[] { typeof(UnityEngine.GameObject) })]
70+
private static void PreviewClassDataConstructorPostfix(ActionGame.PreviewClassData __instance)
71+
#elif KKS
72+
[HarmonyPatch(typeof(ActionGame.PreviewClassData), nameof(ActionGame.PreviewClassData.Awake))]
73+
private static void PreviewClassAwakePostfix(ActionGame.PreviewClassData __instance)
74+
#endif
75+
{
76+
var observer = __instance.button.gameObject.GetOrAddComponent<ClickObserver>();
77+
observer.previewData = __instance;
78+
}
79+
}
80+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using ExtensibleSaveFormat;
2+
using MessagePack;
3+
using MessagePack.Formatters;
4+
5+
namespace SpawnLocker
6+
{
7+
[MessagePackObject]
8+
public class SpawnLockData
9+
{
10+
[Key(0)]
11+
public bool isLocked;
12+
13+
[IgnoreMember]
14+
private static readonly string PluginKey = "SpawnLock";
15+
16+
[IgnoreMember]
17+
private static readonly IFormatterResolver resolver = UnityEngineTypeFormatterResolver.Instance;
18+
19+
public static SpawnLockData Load(PluginData data)
20+
{
21+
if (data?.data == null)
22+
return null;
23+
24+
if (!data.data.TryGetValue(PluginKey, out object bytesObj))
25+
return null;
26+
27+
if (bytesObj is byte[] bytes)
28+
return LZ4MessagePackSerializer.Deserialize<SpawnLockData>(bytes, resolver);
29+
30+
return null;
31+
}
32+
33+
public PluginData Save()
34+
{
35+
PluginData pluginData = new PluginData();
36+
37+
var bytes = LZ4MessagePackSerializer.Serialize(this, resolver);
38+
pluginData.data.Add(PluginKey, bytes);
39+
40+
return pluginData;
41+
}
42+
}
43+
44+
public class UnityEngineTypeFormatterResolver : IFormatterResolver
45+
{
46+
public IMessagePackFormatter<T> GetFormatter<T>()
47+
{
48+
return MessagePack.Unity.UnityResolver.Instance.GetFormatter<T>() ?? MessagePack.Resolvers.StandardResolver.Instance.GetFormatter<T>();
49+
}
50+
51+
public static UnityEngineTypeFormatterResolver Instance = new UnityEngineTypeFormatterResolver();
52+
}
53+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
5+
<HasSharedItems>true</HasSharedItems>
6+
<SharedGUID>A545658E-D416-40B6-90C6-BFD776147D3E</SharedGUID>
7+
</PropertyGroup>
8+
<PropertyGroup Label="Configuration">
9+
<Import_RootNamespace>SpawnLocker</Import_RootNamespace>
10+
</PropertyGroup>
11+
<ItemGroup>
12+
<Compile Include="$(MSBuildThisFileDirectory)AssemblyInfo.cs" />
13+
<Compile Include="$(MSBuildThisFileDirectory)Hooks.cs" />
14+
<Compile Include="$(MSBuildThisFileDirectory)SpawnLockData.cs" />
15+
<Compile Include="$(MSBuildThisFileDirectory)SpawnLockerMain.cs" />
16+
</ItemGroup>
17+
</Project>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// v1.0.0 code was provided by BitMagnet under GPL-3.0 license.
2+
3+
using BepInEx;
4+
using BepInEx.Logging;
5+
using ExtensibleSaveFormat;
6+
using Illusion.Extensions;
7+
using KKAPI;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
11+
namespace SpawnLocker
12+
{
13+
[BepInPlugin(GUID, GUID, Version)]
14+
[BepInProcess(KoikatuAPI.GameProcessName)]
15+
#if KK
16+
[BepInProcess(KoikatuAPI.GameProcessNameSteam)]
17+
#endif
18+
[BepInDependency(KoikatuAPI.GUID, KoikatuAPI.VersionConst)]
19+
public class SpawnLockMain : BaseUnityPlugin
20+
{
21+
public const string PluginName = "Koikatsu Spawn Locker";
22+
public const string GUID = "SpawnLocker";
23+
public const string Version = "1.0.0";
24+
25+
internal static new ManualLogSource Logger;
26+
27+
private void Main()
28+
{
29+
Logger = base.Logger;
30+
31+
Hooks.Apply();
32+
}
33+
34+
public static bool IsLocked(ChaFileControl heroine)
35+
{
36+
if (heroine == null)
37+
return false;
38+
39+
var pluginData = ExtendedSave.GetExtendedDataById(heroine, GUID);
40+
return SpawnLockData.Load(pluginData)?.isLocked == true;
41+
}
42+
43+
public static bool IsLocked(SaveData.Heroine heroine)
44+
{
45+
return IsLocked(heroine?.charFile);
46+
}
47+
48+
public static bool ToggleLock(ChaFileControl heroine)
49+
{
50+
if (heroine == null)
51+
return false;
52+
53+
var data = new SpawnLockData();
54+
data.isLocked = !IsLocked(heroine);
55+
ExtendedSave.SetExtendedDataById(heroine, GUID, data.Save());
56+
57+
Logger.LogMessage("Number of locked heroines: " + _GetLockedHeroines());
58+
59+
return data.isLocked;
60+
}
61+
62+
public static bool ToggleLock(SaveData.Heroine heroine)
63+
{
64+
return ToggleLock(heroine?.charFile);
65+
}
66+
67+
private static int _GetLockedHeroines()
68+
{
69+
#if KK
70+
return Manager.Game.Instance.HeroineList.Count(IsLocked);
71+
#elif KKS
72+
return Manager.Game.HeroineList.Count(IsLocked);
73+
#endif
74+
}
75+
76+
internal static IEnumerable<SaveData.Heroine> _SpawnLockTake(IEnumerable<SaveData.Heroine> heroines, int n)
77+
{
78+
var lockedHeroines = new List<SaveData.Heroine>();
79+
var notLockedHeroines = new List<SaveData.Heroine>();
80+
81+
foreach (var heroine in heroines)
82+
{
83+
if (IsLocked(heroine))
84+
lockedHeroines.Add(heroine);
85+
else
86+
notLockedHeroines.Add(heroine);
87+
}
88+
89+
Logger.LogInfo($"Number of locked heroines: {lockedHeroines.Count}, Number of unlocked heroines: {notLockedHeroines.Count}");
90+
91+
if (lockedHeroines.Count < n)
92+
{
93+
lockedHeroines.AddRange(notLockedHeroines.Take(n - lockedHeroines.Count));
94+
}
95+
96+
return lockedHeroines.Shuffle().Take(n);
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)