Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions KK_Plugins.sln
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HS2.TimelineFlowControl", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AI.TimelineFlowControl", "src\TimelineFlowControl.AI\AI.TimelineFlowControl.csproj", "{2AB45B21-1B6A-40E7-B172-F9A32DD5392D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SpawnLocker", "SpawnLocker", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Core.SpawnLocker", "src\SpawnLocker.Core\Core.SpawnLocker.shproj", "{A545658E-D416-40B6-90C6-BFD776147D3E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KK.SpawnLocker", "src\SpawnLocker.KK\KK.SpawnLocker.csproj", "{1CC189B7-2C5F-460B-C140-D14CFBD39CB8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KKS.SpawnLocker", "src\SpawnLocker.KKS\KKS.SpawnLocker.csproj", "{5A6D7A38-C1FF-7369-EECA-532AD766D270}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1309,6 +1317,14 @@ Global
{2AB45B21-1B6A-40E7-B172-F9A32DD5392D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AB45B21-1B6A-40E7-B172-F9A32DD5392D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AB45B21-1B6A-40E7-B172-F9A32DD5392D}.Release|Any CPU.Build.0 = Release|Any CPU
{1CC189B7-2C5F-460B-C140-D14CFBD39CB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1CC189B7-2C5F-460B-C140-D14CFBD39CB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1CC189B7-2C5F-460B-C140-D14CFBD39CB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1CC189B7-2C5F-460B-C140-D14CFBD39CB8}.Release|Any CPU.Build.0 = Release|Any CPU
{5A6D7A38-C1FF-7369-EECA-532AD766D270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A6D7A38-C1FF-7369-EECA-532AD766D270}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A6D7A38-C1FF-7369-EECA-532AD766D270}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A6D7A38-C1FF-7369-EECA-532AD766D270}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1546,6 +1562,9 @@ Global
{9AC3E3FF-99A8-41F8-BA5D-6C305C08A7A5} = {12FF93A3-5883-44E0-84D3-D65042A6E1E4}
{80D061E6-9CBC-4086-BE30-AFFF166C85AF} = {12FF93A3-5883-44E0-84D3-D65042A6E1E4}
{2AB45B21-1B6A-40E7-B172-F9A32DD5392D} = {12FF93A3-5883-44E0-84D3-D65042A6E1E4}
{A545658E-D416-40B6-90C6-BFD776147D3E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{1CC189B7-2C5F-460B-C140-D14CFBD39CB8} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{5A6D7A38-C1FF-7369-EECA-532AD766D270} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D0F79985-4CB7-46CB-BEC2-FF89C476ED20}
Expand Down Expand Up @@ -1598,6 +1617,8 @@ Global
src\Subtitles.Core\Core.Subtitles.projitems*{19cfff4d-8bda-4c45-9904-28552b32a200}*SharedItemsImports = 5
src\Shared\Shared.projitems*{1c3fd7d8-e631-490a-bcdc-fb04e524d151}*SharedItemsImports = 5
src\StudioImageEmbed.Core\Core.StudioImageEmbed.projitems*{1c3fd7d8-e631-490a-bcdc-fb04e524d151}*SharedItemsImports = 5
src\Shared\Shared.projitems*{1cc189b7-2c5f-460b-c140-d14cfbd39cb8}*SharedItemsImports = 5
src\SpawnLocker.Core\SpawnLocker.Core.projitems*{1cc189b7-2c5f-460b-c140-d14cfbd39cb8}*SharedItemsImports = 5
src\Shared\Shared.projitems*{1d8216cf-b9f0-4463-b120-02c068cc70b6}*SharedItemsImports = 5
src\StudioWindowResize.Core\StudioWindowResize.Core.projitems*{1d8216cf-b9f0-4463-b120-02c068cc70b6}*SharedItemsImports = 5
src\Shared\Shared.projitems*{1e2af057-e10c-4363-902a-db8b843081a8}*SharedItemsImports = 5
Expand Down Expand Up @@ -1715,6 +1736,8 @@ Global
src\StudioWindowResize.Core\StudioWindowResize.Core.projitems*{589ea7e6-340d-4273-8f58-1fd7d6a7d594}*SharedItemsImports = 5
src\AccessoryQuickRemove.Core\AccessoryQuickRemove.Core.projitems*{589fb078-fcf6-4096-a4f3-5fa362e5d0bf}*SharedItemsImports = 5
src\Shared\Shared.projitems*{589fb078-fcf6-4096-a4f3-5fa362e5d0bf}*SharedItemsImports = 5
src\Shared\Shared.projitems*{5a6d7a38-c1ff-7369-eeca-532ad766d270}*SharedItemsImports = 5
src\SpawnLocker.Core\SpawnLocker.Core.projitems*{5a6d7a38-c1ff-7369-eeca-532ad766d270}*SharedItemsImports = 5
src\MaterialEditor.Core\Core.MaterialEditor.projitems*{5a8fdb27-ffca-4a77-ab0f-afdef9a034ed}*SharedItemsImports = 13
src\Pushup.Core\Pushup.Core.projitems*{5b9d6ca7-4a75-4299-8280-9fd16b6f4c89}*SharedItemsImports = 5
src\Shared\Shared.projitems*{5b9d6ca7-4a75-4299-8280-9fd16b6f4c89}*SharedItemsImports = 5
Expand Down Expand Up @@ -1848,6 +1871,7 @@ Global
src\Shared\Shared.projitems*{a0e2000f-196f-4ead-b571-20a4446b7824}*SharedItemsImports = 5
src\StudioImageEmbed.Core\Core.StudioImageEmbed.projitems*{a18c34c6-1220-487a-8448-fc1a8251fc57}*SharedItemsImports = 13
src\ShaderSwapper.Core\ShaderSwapper.Core.projitems*{a27f5e6b-5e24-4609-a634-1a704ebbf1d9}*SharedItemsImports = 13
src\SpawnLocker.Core\SpawnLocker.Core.projitems*{a545658e-d416-40b6-90c6-bfd776147d3e}*SharedItemsImports = 13
src\Profile.Core\Profile.Core.projitems*{a566dc0b-c265-447f-86e0-806e3c8abe46}*SharedItemsImports = 5
src\Shared\Shared.projitems*{a566dc0b-c265-447f-86e0-806e3c8abe46}*SharedItemsImports = 5
src\FreeHRandom.Core\FreeHRandom.Core.projitems*{a59cf955-a07b-4ef1-a9b5-169e0ce750da}*SharedItemsImports = 5
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,14 @@ Custom rules for swapping shaders can be provided in xml files. Check the "Mappi

</details>

#### SpawnLocker

- [KK_SpawnLocker]
- [KKS_SpawnLocker]

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).
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.

#### FreeHStudioSceneLoader

- [KK_FreeHStudioSceneLoader]
Expand Down Expand Up @@ -959,3 +967,5 @@ Allows you to use studio scenes as H mode maps in main game. **Experimental! Exp
[KKS_ClothesToAccessories]: https://github.com/IllusionMods/KK_Plugins/releases/download/v259/KKS_ClothesToAccessories.v1.1.2.zip "v1.1.2"
[KK_Boop]: https://github.com/IllusionMods/KK_Plugins/releases/download/v250/KK_Boop.v2.1.zip "v2.1"
[KKS_Boop]: https://github.com/IllusionMods/KK_Plugins/releases/download/v250/KKS_Boop.v2.1.zip "v2.1"
[KK_SpawnLocker]: https://github.com/IllusionMods/KK_Plugins/releases "v0.0"
[KKS_SpawnLocker]: https://github.com/IllusionMods/KK_Plugins/releases "v0.0"
9 changes: 9 additions & 0 deletions src/SpawnLocker.Core/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using KK_Plugins;
using System.Reflection;
using static SpawnLocker.SpawnLockMain;

[assembly: AssemblyTitle(PluginName)]
[assembly: AssemblyProduct(PluginName)]
[assembly: AssemblyDescription(GUID + " for " + Constants.GameName)]
[assembly: AssemblyVersion(Version)]
[assembly: AssemblyFileVersion(Version)]
13 changes: 13 additions & 0 deletions src/SpawnLocker.Core/Core.SpawnLocker.shproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{A545658E-D416-40B6-90C6-BFD776147D3E}</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="SpawnLocker.Core.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>
80 changes: 80 additions & 0 deletions src/SpawnLocker.Core/Hooks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using HarmonyLib;
using KKAPI.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace SpawnLocker
{
internal static class Hooks
{
public static void Apply()
{
var harmony = Harmony.CreateAndPatchAll(typeof(Hooks), SpawnLockMain.GUID);

var transpiler = new HarmonyMethod(typeof(Hooks), nameof(Hooks.NPCLoadAllTranspile));

foreach (var targetMethod in typeof(ActionScene).GetMethods(AccessTools.all).Where(x => x.Name == nameof(ActionScene.NPCLoadAll)))
{
SpawnLockMain.Logger.LogDebug("Patching: " + targetMethod.FullDescription());
harmony.PatchMoveNext(targetMethod, transpiler: transpiler);
}
}

private static IEnumerable<CodeInstruction> NPCLoadAllTranspile(IEnumerable<CodeInstruction> instructions)
{
var targetMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.Take)).MakeGenericMethod(typeof(SaveData.Heroine));
var newTakeMethod = AccessTools.Method(typeof(SpawnLockMain), nameof(SpawnLockMain._SpawnLockTake));

foreach (var instruction in instructions)
{
if (instruction.opcode != OpCodes.Call || instruction.operand as MethodInfo != targetMethod)
{
yield return instruction;
}
else
{
yield return new CodeInstruction(OpCodes.Call, newTakeMethod);
}
}
}

[HarmonyPostfix]
[HarmonyPatch(typeof(ActionGame.PreviewClassData), nameof(ActionGame.PreviewClassData.Set))]
private static void PreviewClassDataSetPostfix(ActionGame.PreviewClassData __instance, SaveData.CharaData charaData)
{
_UpdateStatus(__instance);
}

[HarmonyPostfix]
[HarmonyPatch(typeof(ActionGame.PreviewClassData), nameof(ActionGame.PreviewClassData.Clear))]
private static void PreviewClassDataClearPostfix(ActionGame.PreviewClassData __instance)
{
_UpdateStatus(__instance);
}

static void _UpdateStatus(ActionGame.PreviewClassData __instance)
{
var observer = __instance.button.GetComponent<ClickObserver>();

if (observer != null)
{
observer.UpdateStatus();
}
}

[HarmonyPostfix]
#if KK
[HarmonyPatch(typeof(ActionGame.PreviewClassData), MethodType.Constructor, new System.Type[] { typeof(UnityEngine.GameObject) })]
private static void PreviewClassDataConstructorPostfix(ActionGame.PreviewClassData __instance)
#elif KKS
[HarmonyPatch(typeof(ActionGame.PreviewClassData), nameof(ActionGame.PreviewClassData.Awake))]
private static void PreviewClassAwakePostfix(ActionGame.PreviewClassData __instance)
#endif
{
var observer = __instance.button.gameObject.GetOrAddComponent<ClickObserver>();
observer.previewData = __instance;
}
}
}
53 changes: 53 additions & 0 deletions src/SpawnLocker.Core/SpawnLockData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using ExtensibleSaveFormat;
using MessagePack;
using MessagePack.Formatters;

namespace SpawnLocker
{
[MessagePackObject]
public class SpawnLockData
{
[Key(0)]
public bool isLocked;

[IgnoreMember]
private static string PluginKey = "SpawnLock";

[IgnoreMember]
private static IFormatterResolver resolver = UnityEngineTypeFormatterResolver.Instance;

public static SpawnLockData Load(PluginData data)
{
if (data?.data == null)
return null;

if (!data.data.TryGetValue(PluginKey, out object bytesObj))
return null;

if (bytesObj is byte[] bytes)
return LZ4MessagePackSerializer.Deserialize<SpawnLockData>(bytes, resolver);

return null;
}

public PluginData Save()
{
PluginData pluginData = new PluginData();

var bytes = LZ4MessagePackSerializer.Serialize(this, resolver);
pluginData.data.Add(PluginKey, bytes);

return pluginData;
}
}

public class UnityEngineTypeFormatterResolver : IFormatterResolver
{
public IMessagePackFormatter<T> GetFormatter<T>()
{
return MessagePack.Unity.UnityResolver.Instance.GetFormatter<T>() ?? MessagePack.Resolvers.StandardResolver.Instance.GetFormatter<T>();
}

public static UnityEngineTypeFormatterResolver Instance = new UnityEngineTypeFormatterResolver();
}
}
17 changes: 17 additions & 0 deletions src/SpawnLocker.Core/SpawnLocker.Core.projitems
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>a27f5e6b-5e24-4609-a634-1a704ebbf1d9</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>ShaderSwapper.Core</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)AssemblyInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Hooks.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SpawnLockData.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SpawnLockerMain.cs" />
</ItemGroup>
</Project>
99 changes: 99 additions & 0 deletions src/SpawnLocker.Core/SpawnLockerMain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// v1.0.0 code was provided by BitMagnet under GPL-3.0 license.

using BepInEx;
using BepInEx.Logging;
using ExtensibleSaveFormat;
using Illusion.Extensions;
using KKAPI;
using System.Collections.Generic;
using System.Linq;

namespace SpawnLocker
{
[BepInPlugin(GUID, GUID, Version)]
[BepInProcess(KoikatuAPI.GameProcessName)]
#if KK
[BepInProcess(KoikatuAPI.GameProcessNameSteam)]
#endif
[BepInDependency(KoikatuAPI.GUID, KoikatuAPI.VersionConst)]
public class SpawnLockMain : BaseUnityPlugin
{
public const string PluginName = "Koikatsu Spawn Locker";
public const string GUID = "SpawnLocker";
public const string Version = "1.0.0";

internal static new ManualLogSource Logger;

private void Main()
{
Logger = base.Logger;

Hooks.Apply();
}

public static bool IsLocked(ChaFileControl heroine)
{
if (heroine == null)
return false;

var pluginData = ExtendedSave.GetExtendedDataById(heroine, GUID);
return SpawnLockData.Load(pluginData)?.isLocked == true;
}

public static bool IsLocked(SaveData.Heroine heroine)
{
return IsLocked(heroine?.charFile);
}

public static bool ToggleLock(ChaFileControl heroine)
{
if (heroine == null)
return false;

var data = new SpawnLockData();
data.isLocked = !IsLocked(heroine);
ExtendedSave.SetExtendedDataById(heroine, GUID, data.Save());

Logger.LogMessage("Number of locked heroines: " + _GetLockedHeroines());

return data.isLocked;
}

public static bool ToggleLock(SaveData.Heroine heroine)
{
return ToggleLock(heroine?.charFile);
}

private static int _GetLockedHeroines()
{
#if KK
return Manager.Game.Instance.HeroineList.Count(IsLocked);
#elif KKS
return Manager.Game.HeroineList.Count(IsLocked);
#endif
}

internal static IEnumerable<SaveData.Heroine> _SpawnLockTake(IEnumerable<SaveData.Heroine> heroines, int n)
{
var lockedHeroines = new List<SaveData.Heroine>();
var notLockedHeroines = new List<SaveData.Heroine>();

foreach (var heroine in heroines)
{
if (IsLocked(heroine))
lockedHeroines.Add(heroine);
else
notLockedHeroines.Add(heroine);
}

Logger.LogInfo($"Number of locked heroines: {lockedHeroines.Count}, Number of unlocked heroines: {notLockedHeroines.Count}");

if (lockedHeroines.Count < n)
{
lockedHeroines.AddRange(notLockedHeroines.Take(n - lockedHeroines.Count));
}

return lockedHeroines.Shuffle().Take(n);
}
}
}
Loading
Loading