├── linker.xml
├── src
├── SkipAllRefs.targets
├── AnimationLoader.KoikatsuSunshine
│ ├── Plugin.cs
│ ├── AnimationLoaderGameController.cs
│ ├── AnimationLoader.KoikatsuSunshine.csproj
│ ├── GlobalSuppressions.cs
│ ├── UsedAnimations.cs
│ ├── LoadXML.Overrides.cs
│ └── Hooks.LoadMotionList.cs
├── AnimationLoader.Koikatu
│ ├── Plugin.cs
│ ├── AnimationLoaderGameController.cs
│ ├── GlobalSuppressions.cs
│ ├── AnimationLoader.Koikatu.csproj
│ └── Hooks.LoadMotionList.cs
├── BuildSettings.Common.props
└── AnimationLoader.Core
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── AnimatorOverrideController.cs
│ ├── AnimationLoader.Core.shproj
│ ├── Plugin.Info.cs
│ ├── Utils
│ ├── TextScroll.cs
│ ├── Log.cs
│ ├── AnimationUsage.cs
│ ├── Extensions.cs
│ └── Utilities.cs
│ ├── AnimationLoader.Core.projitems
│ ├── FootJobAnimations.cs
│ ├── LoadXML.VersionChecks.cs
│ ├── Fixes
│ ├── Move.Controller.cs
│ └── Move.AnimationInfo.cs
│ ├── Hooks.cs
│ ├── LoadXML.cs
│ ├── Plugin.cs
│ ├── SwapAnimationInfo.cs
│ ├── LoadXML.ProcessArray.cs
│ ├── AnimationsNames.cs
│ ├── Plugin.ConfigEntries.cs
│ ├── Hooks.Animators.cs
│ ├── AnimationClipsCache.cs
│ └── Hooks.AnimationLists.cs
├── nuget.config
├── AnimationLoader.sln
├── README.md
├── CHANGELOG.md
├── .gitignore
└── Manifest.md
/linker.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/SkipAllRefs.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/AnimationLoader.KoikatsuSunshine/Plugin.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Entry point for AnimationLoader.KoikatsuSunshine
3 | //
4 | using BepInEx;
5 |
6 | using KKAPI;
7 |
8 |
9 | namespace AnimationLoader
10 | {
11 | [BepInDependency(Sideloader.Sideloader.GUID, Sideloader.Sideloader.Version)]
12 | [BepInDependency(KoikatuAPI.GUID, KoikatuAPI.VersionConst)]
13 | [BepInPlugin(GUID, PluginDisplayName, Version)]
14 | [BepInProcess(KoikatuAPI.GameProcessName)]
15 | [BepInProcess(KoikatuAPI.StudioProcessName)]
16 | [BepInProcess(KoikatuAPI.VRProcessName)]
17 | public partial class SwapAnim : BaseUnityPlugin
18 | {
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Koikatu/Plugin.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Entry point for AnimationLoader.Koikatu
3 | //
4 | using BepInEx;
5 |
6 | using KKAPI;
7 |
8 |
9 | namespace AnimationLoader
10 | {
11 | [BepInDependency(KoikatuAPI.GUID, KoikatuAPI.VersionConst)]
12 | [BepInDependency(Sideloader.Sideloader.GUID, Sideloader.Sideloader.Version)]
13 | [BepInProcess(KoikatuAPI.GameProcessName)]
14 | [BepInProcess(KoikatuAPI.GameProcessNameSteam)]
15 | [BepInProcess(KoikatuAPI.StudioProcessName)]
16 | [BepInProcess(KoikatuAPI.VRProcessName)]
17 | [BepInProcess(KoikatuAPI.VRProcessNameSteam)]
18 | [BepInPlugin(GUID, PluginDisplayName, Version)]
19 | public partial class SwapAnim : BaseUnityPlugin
20 | {
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/BuildSettings.Common.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | embedded
5 | false
6 | false
7 | $(SolutionDir)bin\$(Configuration)
8 | true
9 | latest
10 | latest
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("https://gitgoon.dev/IllusionMods/AnimationLoader")]
9 | [assembly: AssemblyCopyright("Copyright ©IllusionMods 2021-2022")]
10 | [assembly: AssemblyTrademark("")]
11 | [assembly: AssemblyCulture("")]
12 |
13 | // Setting ComVisible to false makes the types in this assembly not visible
14 | // to COM components. If you need to access a type in this assembly from
15 | // COM, set the ComVisible attribute to true on that type.
16 | [assembly: ComVisible(false)]
17 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Koikatu/AnimationLoaderGameController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using UnityEngine;
4 |
5 | using KKAPI.MainGame;
6 |
7 | using static AnimationLoader.SwapAnim;
8 |
9 | namespace AnimationLoader
10 | {
11 | ///
12 | /// Save use animations.
13 | ///
14 | internal class AnimationLoaderGameController : GameCustomFunctionController
15 | {
16 | protected override void OnEndH(MonoBehaviour proc, HFlag flags, bool vr)
17 | {
18 | if (flags.isFreeH)
19 | {
20 | return;
21 | }
22 |
23 | try
24 | {
25 | _animationsUseStats.Save();
26 | }
27 | catch (Exception ex)
28 | {
29 | Log.Error($"0033: Error saving used animations - {ex}");
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/AnimationLoader.KoikatsuSunshine/AnimationLoaderGameController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using UnityEngine;
4 |
5 | using KKAPI.MainGame;
6 |
7 | using static AnimationLoader.SwapAnim;
8 |
9 | namespace AnimationLoader
10 | {
11 | ///
12 | /// Save use animations.
13 | ///
14 | internal class AnimationLoaderGameController : GameCustomFunctionController
15 | {
16 | protected override void OnEndH(MonoBehaviour proc, HFlag flags, bool vr)
17 | {
18 | if (flags.isFreeH)
19 | {
20 | return;
21 | }
22 |
23 | try
24 | {
25 | _usedAnimations.Save();
26 | _animationsUseStats.Save();
27 | }
28 | catch (Exception ex)
29 | {
30 | Log.Error($"0033: Error saving used animations - {ex}");
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/AnimatorOverrideController.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | using UnityEngine;
4 |
5 | namespace AnimationLoader
6 | {
7 | public partial class SwapAnim
8 | {
9 | private static AnimatorOverrideController SetupAnimatorOverrideController(
10 | RuntimeAnimatorController src,
11 | RuntimeAnimatorController over)
12 | {
13 | if (src == null || over == null)
14 | {
15 | return null;
16 | }
17 |
18 | var aoc = new AnimatorOverrideController(src);
19 | var target = new AnimatorOverrideController(over);
20 | foreach (var ac in src.animationClips.Where(x => x != null)) //thanks omega/katarsys
21 | {
22 | aoc[ac.name] = ac;
23 | }
24 |
25 | foreach (var ac in target.animationClips.Where(x => x != null)) //thanks omega/katarsys
26 | {
27 | aoc[ac.name] = ac;
28 | }
29 |
30 | aoc.name = over.name;
31 | return aoc;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/AnimationLoader.Core.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | e48d1a5f-a706-4293-88bb-d82bd68cdfed
5 | 14.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Plugin.Info.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | using AnimationLoader;
4 |
5 | #region Assembly attributes
6 |
7 | /*
8 | * These attributes define various meta-information of the generated DLL.
9 | * In general, you don't need to touch these. Instead, edit the values in Info.
10 | */
11 | [assembly: AssemblyTitle(SwapAnim.PluginName + " (" + SwapAnim.GUID + ")")]
12 | [assembly: AssemblyProduct(SwapAnim.PluginName)]
13 | [assembly: AssemblyVersion(SwapAnim.Version)]
14 | [assembly: AssemblyFileVersion(SwapAnim.Version)]
15 |
16 | #endregion Assembly attributes
17 |
18 |
19 | namespace AnimationLoader
20 | {
21 | public partial class SwapAnim
22 | {
23 | public const string GUID = "essuhauled.animationloader";
24 | #if DEBUG
25 | public const string PluginDisplayName = "Animation Loader (Debug)";
26 | #else
27 | public const string PluginDisplayName = "Animation Loader";
28 | #endif
29 | public const string Version = "1.1.3.4";
30 | #if KK
31 | public const string PluginName = "AnimationLoader.Koikatu";
32 | #else
33 | public const string PluginName = "AnimationLoader.KoikatsuSunshine";
34 | #endif
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Utils/TextScroll.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using TMPro;
4 | using UnityEngine;
5 | using UnityEngine.EventSystems;
6 |
7 |
8 | namespace AnimationLoader
9 | {
10 | public class TextScroll : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
11 | {
12 | public RectTransform transBase;
13 | public TextMeshProUGUI textMesh;
14 | public float speed = 70f;
15 | private bool move;
16 |
17 | public void OnPointerEnter(PointerEventData eventData)
18 | {
19 | move = true;
20 | StartCoroutine(MoveText());
21 | }
22 |
23 | public void OnPointerExit(PointerEventData eventData)
24 | {
25 | move = false;
26 | MarginSet(0f);
27 | }
28 |
29 | private IEnumerator MoveText()
30 | {
31 | while(move)
32 | {
33 | if (textMesh.preferredWidth - Math.Abs(textMesh.margin.x)
34 | < transBase.sizeDelta.x * 0.5f)
35 | {
36 | MarginSet(transBase.sizeDelta.x * 0.5f);
37 | }
38 | else
39 | {
40 | MarginAdd(-speed * Time.deltaTime);
41 | }
42 | yield return null;
43 | }
44 | }
45 |
46 | private void MarginAdd(float value)
47 | {
48 | var margin = textMesh.margin;
49 | margin.x += value;
50 | textMesh.margin = margin;
51 | }
52 |
53 | private void MarginSet(float value)
54 | {
55 | var margin = textMesh.margin;
56 | margin.x = value;
57 | textMesh.margin = margin;
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Koikatu/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("Style", "IDE0022:Use block body for methods", Justification = "Style use by community", Scope = "member", Target = "~M:AnimationLoader.SwapAnim.GetMoveController(ChaControl)~AnimationLoader.SwapAnim.MoveController")]
9 | //[assembly: SuppressMessage("Style", "IDE0022:Use block body for methods", Justification = "Style use by community", Scope = "member", Target = "~M:AnimationLoader.SwapAnim.MoveController.SetOriginalPosition")]
10 | [assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "", Scope = "member", Target = "~M:AnimationLoader.SwapAnim.ReadNames(AnimationLoader.SwapAnim.Names@,System.String)")]
11 | [assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "", Scope = "member", Target = "~M:AnimationLoader.SwapAnim.SaveNamesXmls")]
12 | [assembly: SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Variable modified in code", Scope = "member", Target = "~F:AnimationLoader.SwapAnim.Hooks._animationClipsCache")]
13 | [assembly: SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Variable modified in code", Scope = "member", Target = "~F:AnimationLoader.SwapAnim.Hooks._animationClipsByType")]
14 | [assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "It is used", Scope = "member", Target = "~F:AnimationLoader.SwapAnim._specialAnimation")]
15 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Koikatu/AnimationLoader.Koikatu.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | net35
9 | AnimationLoader
10 | $(MSBuildProjectName)
11 |
12 |
13 |
14 | TRACE;DEBUG;KK
15 |
16 |
17 |
18 | TRACE;KK
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | all
32 | runtime; build; native; contentfiles; analyzers; buildtransitive
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/AnimationLoader.KoikatsuSunshine/AnimationLoader.KoikatsuSunshine.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | net46
9 | AnimationLoader
10 | $(MSBuildProjectName)
11 | warnings
12 |
13 |
14 |
15 | TRACE;DEBUG;KKS
16 |
17 |
18 |
19 | TRACE;KKS
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | all
34 | runtime; build; native; contentfiles; analyzers; buildtransitive
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/AnimationLoader.KoikatsuSunshine/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | //[assembly: SuppressMessage("Style", "IDE0022:Use block body for methods", Justification = "Community usage", Scope = "member", Target = "~M:AnimationLoader.SwapAnim.MoveController.SetOriginalPosition")]
9 | [assembly: SuppressMessage("Style", "IDE0022:Use block body for methods", Justification = "Community usage", Scope = "member", Target = "~M:AnimationLoader.SwapAnim.GetMoveController(ChaControl)~AnimationLoader.SwapAnim.MoveController")]
10 | [assembly: SuppressMessage("Performance", "CA1825:Avoid zero-length array allocations", Justification = "KK does not have Array.Empty", Scope = "member", Target = "~F:AnimationLoader.OverrideInfo.categories")]
11 | [assembly: SuppressMessage("Performance", "CA1825:Avoid zero-length array allocations", Justification = "KK does not have Array.Empty", Scope = "member", Target = "~F:AnimationLoader.SwapAnimationInfo.categories")]
12 | [assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Leave for reference", Scope = "member", Target = "~M:AnimationLoader.SwapAnim.SaveNamesXmls")]
13 | [assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Leave for reference", Scope = "member", Target = "~M:AnimationLoader.SwapAnim.ReadNames(AnimationLoader.SwapAnim.Names@,System.String)")]
14 | [assembly: SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "", Scope = "member", Target = "~F:AnimationLoader.SwapAnim.Hooks._animationClipsCache")]
15 | [assembly: SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "", Scope = "member", Target = "~F:AnimationLoader.SwapAnim.Hooks._animationClipsByType")]
16 | [assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "", Scope = "member", Target = "~M:AnimationLoader.SwapAnim.Hooks.LoadAddTaiiPostfix(System.Object,System.Collections.Generic.List{AddTaiiData.Param})")]
17 |
--------------------------------------------------------------------------------
/src/AnimationLoader.KoikatsuSunshine/UsedAnimations.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Save key of used animations
3 | //
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Xml.Serialization;
10 |
11 |
12 | namespace AnimationLoader
13 | {
14 | [XmlRoot("Animations")]
15 | [Serializable]
16 | public class UsedAnimations
17 | {
18 | [XmlElement]
19 | public HashSet Keys = [];
20 |
21 | private static readonly string _path = Path.Combine(UserData.Path, "save");
22 | private static readonly string _fileName = $"{_path}/animations.xml";
23 | private static readonly XmlSerializer _xmlSerializer = new(typeof(UsedAnimations));
24 | private static readonly FileInfo _fileInfo = new(_fileName);
25 |
26 | public override string ToString()
27 | {
28 | var sb = new StringBuilder();
29 | var total = Keys.Count;
30 | var count = 0;
31 |
32 | foreach (var animation in Keys.OrderBy(x => x))
33 | {
34 | count++;
35 | if (count == total)
36 | {
37 | sb.Append(animation);
38 | }
39 | else
40 | {
41 | sb.Append($"{animation}, ");
42 | }
43 | }
44 | return "{ " + sb.ToString() + " }";
45 | }
46 |
47 | public void Save()
48 | {
49 | _fileInfo.Directory.Create();
50 | StreamWriter writer = new(_fileName);
51 | _xmlSerializer.Serialize(writer.BaseStream, this);
52 | writer.Close();
53 | }
54 |
55 | public void Read()
56 | {
57 | if (_fileInfo.Exists)
58 | {
59 | StreamReader reader = new(_fileName);
60 | var tmp = (UsedAnimations)_xmlSerializer.Deserialize(reader.BaseStream);
61 | reader.Close();
62 | // This can be removed later for some reason was using a List instead of
63 | // a HashSet Removing duplicates.
64 | foreach (var e in tmp.Keys)
65 | {
66 | Keys.Add(e);
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/AnimationLoader.Core.projitems:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 | true
6 | e48d1a5f-a706-4293-88bb-d82bd68cdfed
7 |
8 |
9 | AnimationLoader.Core
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/AnimationLoader.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31919.166
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6B1C4EAA-7ADD-47A9-AC1E-004D83C9BC6C}"
7 | ProjectSection(SolutionItems) = preProject
8 | .editorconfig = .editorconfig
9 | .gitignore = .gitignore
10 | src\BuildSettings.Common.props = src\BuildSettings.Common.props
11 | CHANGELOG.md = CHANGELOG.md
12 | Manifest.md = Manifest.md
13 | nuget.config = nuget.config
14 | README.md = README.md
15 | src\SkipAllRefs.targets = src\SkipAllRefs.targets
16 | template.xml = template.xml
17 | EndProjectSection
18 | EndProject
19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnimationLoader.KoikatsuSunshine", "src\AnimationLoader.KoikatsuSunshine\AnimationLoader.KoikatsuSunshine.csproj", "{269D192E-9499-429F-A07F-C8B7E13DC861}"
20 | EndProject
21 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "AnimationLoader.Core", "src\AnimationLoader.Core\AnimationLoader.Core.shproj", "{E48D1A5F-A706-4293-88BB-D82BD68CDFED}"
22 | EndProject
23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnimationLoader.Koikatu", "src\AnimationLoader.Koikatu\AnimationLoader.Koikatu.csproj", "{3EF70928-3925-4F6C-96E9-0EF3B9228E0D}"
24 | EndProject
25 | Global
26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
27 | Debug|Any CPU = Debug|Any CPU
28 | Release|Any CPU = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
31 | {269D192E-9499-429F-A07F-C8B7E13DC861}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {269D192E-9499-429F-A07F-C8B7E13DC861}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {269D192E-9499-429F-A07F-C8B7E13DC861}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {269D192E-9499-429F-A07F-C8B7E13DC861}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {3EF70928-3925-4F6C-96E9-0EF3B9228E0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {3EF70928-3925-4F6C-96E9-0EF3B9228E0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {3EF70928-3925-4F6C-96E9-0EF3B9228E0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {3EF70928-3925-4F6C-96E9-0EF3B9228E0D}.Release|Any CPU.Build.0 = Release|Any CPU
39 | EndGlobalSection
40 | GlobalSection(SolutionProperties) = preSolution
41 | HideSolutionNode = FALSE
42 | EndGlobalSection
43 | GlobalSection(ExtensibilityGlobals) = postSolution
44 | SolutionGuid = {F34D347D-7842-4922-A372-47ECD316CD6E}
45 | EndGlobalSection
46 | GlobalSection(SharedMSBuildProjectFiles) = preSolution
47 | src\AnimationLoader.Core\AnimationLoader.Core.projitems*{269d192e-9499-429f-a07f-c8b7e13dc861}*SharedItemsImports = 5
48 | src\AnimationLoader.Core\AnimationLoader.Core.projitems*{3ef70928-3925-4f6c-96e9-0ef3b9228e0d}*SharedItemsImports = 5
49 | src\AnimationLoader.Core\AnimationLoader.Core.projitems*{e48d1a5f-a706-4293-88bb-d82bd68cdfed}*SharedItemsImports = 13
50 | EndGlobalSection
51 | EndGlobal
52 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Utils/Log.cs:
--------------------------------------------------------------------------------
1 | //
2 | // class to control the display of logs
3 | // Missing rotating logs :( have to do something about that
4 | //
5 | using BepInEx.Logging;
6 |
7 |
8 | ///
9 | /// Show logs when enabled
10 | ///
11 | internal static class Log
12 | {
13 | private static ManualLogSource _logSource;
14 | private static bool _enabled = false;
15 | private static bool _debugToConsole = false;
16 |
17 | public static bool Enabled {
18 | get
19 | {
20 | return _enabled;
21 | }
22 |
23 | set
24 | {
25 | _enabled = value;
26 | }
27 | }
28 |
29 | public static bool DebugToConsole {
30 | get
31 | {
32 | return _debugToConsole;
33 | }
34 | set
35 | {
36 | _debugToConsole = value;
37 | }
38 | }
39 |
40 | public static ManualLogSource LogSource {
41 | get
42 | {
43 | return _logSource;
44 | }
45 | set
46 | {
47 | _logSource = value;
48 | }
49 | }
50 |
51 | public static void SetLogSource(ManualLogSource logSource)
52 | {
53 | _logSource = logSource;
54 | }
55 |
56 | public static void Info(object data)
57 | {
58 | if (_enabled)
59 | {
60 | _logSource.LogInfo(data);
61 | }
62 | }
63 |
64 | public static void Debug(object data)
65 | {
66 | if (_enabled)
67 | {
68 | if (_debugToConsole)
69 | {
70 | _logSource.LogInfo(data);
71 | }
72 | else
73 | {
74 | _logSource.LogDebug(data);
75 | }
76 | }
77 | }
78 |
79 | public static void Error(object data)
80 | {
81 | if (_enabled)
82 | {
83 | if (_debugToConsole)
84 | {
85 | _logSource.LogError(data);
86 | }
87 | else
88 | {
89 | _logSource.LogError(data);
90 | }
91 | }
92 | }
93 |
94 | public static void Fatal(object data)
95 | {
96 | if (_enabled)
97 | {
98 | _logSource.LogFatal(data);
99 | }
100 | }
101 |
102 | public static void Message(object data)
103 | {
104 | if (_enabled)
105 | {
106 | _logSource.LogMessage(data);
107 | }
108 | }
109 |
110 | public static void Warning(object data)
111 | {
112 | if (_enabled)
113 | {
114 | _logSource.LogWarning(data);
115 | }
116 | }
117 |
118 | ///
119 | /// For logs that should not depend on the logs been enabled
120 | ///
121 | ///
122 | ///
123 | public static void Level(LogLevel level, object data)
124 | {
125 | _logSource.Log(level, data);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/FootJobAnimations.cs:
--------------------------------------------------------------------------------
1 | //
2 | // FootJobAnimations.cs
3 | //
4 |
5 | using System.Collections;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Runtime.Serialization;
9 | using System.Text;
10 |
11 | using BepInEx;
12 |
13 | namespace AnimationLoader
14 | {
15 | public partial class SwapAnim
16 | {
17 | internal static FootJobAnimations _footJobAnimations = [];
18 |
19 | [DataContract(Name = "FootJobAnimations", Namespace = "https://gitgoon.dev/IllusionMods/AnimationLoader")]
20 | public class FootJobAnimations : IEnumerable
21 | {
22 | [DataMember]
23 | private HashSet Animations;
24 |
25 | private static readonly string _path = Path.Combine(Paths.ConfigPath, "AnimationLoader/FootJob");
26 | private static readonly string _fileName = $"{_path}/FootJobAnimations.xml";
27 | private static readonly DataContractSerializer _serializer = new(typeof(FootJobAnimations));
28 | private static readonly FileInfo _fileInfo = new(_fileName);
29 |
30 | public int Count => Animations.Count;
31 |
32 | public FootJobAnimations()
33 | {
34 | Animations = [];
35 | Animations.Clear();
36 | }
37 |
38 | public IEnumerator GetEnumerator()
39 | {
40 | return Animations.GetEnumerator();
41 | }
42 |
43 | public void Add(string item)
44 | {
45 | Animations.Add(item);
46 | }
47 |
48 | public void Clear()
49 | {
50 | Animations.Clear();
51 | }
52 |
53 | public bool Contains(string item)
54 | {
55 | return Animations.Contains(item);
56 | }
57 |
58 | public void Save()
59 | {
60 | _fileInfo.Directory.Create();
61 | var writer = new FileStream(_fileName, FileMode.Create, FileAccess.Write);
62 | _serializer.WriteObject(writer, this);
63 | writer.Close();
64 | }
65 |
66 | public void Read()
67 | {
68 | if (_fileInfo.Exists)
69 | {
70 | using var fileStream = System.IO.File.Open(_fileName, FileMode.Open, FileAccess.Read);
71 | var tmp = _serializer.ReadObject(fileStream) as FootJobAnimations;
72 | fileStream.Close();
73 |
74 | Animations = tmp?.Animations;
75 | }
76 | else
77 | {
78 | Log.Error("[FootJobAnimations.Read] File not found.");
79 | }
80 | #if DEBUG
81 | var logLines = new StringBuilder();
82 |
83 | foreach (var fj in _footJobAnimations)
84 | {
85 | logLines.AppendLine(fj.ToString());
86 | }
87 | Log.Debug($"0012: Foot jobs.\n{logLines}");
88 | #endif
89 | }
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Utils/AnimationUsage.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Save key of used animations
3 | //
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.IO;
8 | using System.Runtime.Serialization;
9 | using System.Text;
10 |
11 | using static AnimationLoader.SwapAnim;
12 |
13 | namespace AnimationLoader
14 | {
15 | [DataContract(Name = "AnimationsUsage", Namespace = "https://gitgoon.dev/IllusionMods/AnimationLoader")]
16 | public class AnimationsUseStats
17 | {
18 | [DataMember]
19 | public Dictionary Stats { set; get; }
20 |
21 | private static readonly string _path = Path.Combine(UserData.Path, "AnimationLoader/Usage");
22 | private static readonly string _fileName = $"{_path}/AnimationsUsage.xml";
23 | private static readonly DataContractSerializer _serializer = new(typeof(AnimationsUseStats));
24 | private static readonly FileInfo _fileInfo = new(_fileName);
25 |
26 | public int this[string key]
27 | {
28 | get { return Stats[key]; }
29 | set { Stats[key] = value; }
30 | }
31 |
32 | public int Count => Stats.Count;
33 |
34 | public AnimationsUseStats()
35 | {
36 | Stats = [];
37 | Stats.Clear();
38 | }
39 |
40 | public bool TryGetValue(
41 | string key,
42 | out int value)
43 | {
44 | return Stats.TryGetValue(key, out value);
45 | }
46 |
47 | public void Init(List[] lstAnimInfo)
48 | {
49 | foreach (var c in lstAnimInfo)
50 | {
51 | foreach (var a in c)
52 | {
53 | var key = GetAnimationKey(a);
54 | if (!Stats.ContainsKey(key))
55 | {
56 | Stats.Add(key, 0);
57 | }
58 | }
59 | }
60 | }
61 |
62 | public override string ToString()
63 | {
64 | var animations = new StringBuilder();
65 |
66 | foreach (var e in Stats)
67 | {
68 | animations.AppendLine($"Key={e.Key} Value={e.Value}");
69 | }
70 |
71 | return animations.ToString();
72 | }
73 |
74 | public void Save()
75 | {
76 | _fileInfo.Directory.Create();
77 | var writer = new FileStream(_fileName, FileMode.Create, FileAccess.Write);
78 | _serializer.WriteObject(writer, this);
79 | writer.Close();
80 | }
81 |
82 | public void Read()
83 | {
84 | if (_fileInfo.Exists)
85 | {
86 | using var fileStream = File.Open(_fileName, FileMode.Open, FileAccess.Read);
87 | var tmp = _serializer.ReadObject(fileStream) as AnimationsUseStats;
88 | fileStream.Close();
89 |
90 | Stats = tmp?.Stats;
91 | }
92 | }
93 |
94 | public IOrderedEnumerable> Sorted()
95 | {
96 | var sortedByUsage = Stats
97 | .OrderByDescending(u => u.Value)
98 | .ThenBy(n => n.Key);
99 |
100 | return sortedByUsage;
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AnimationLoader
2 | A plugin for loading animations from Sideloader zipmods.
3 | Thank you Essu for the [main bulk of the code](https://gitgoon.dev/IllusionMods/AnimationLoader/commit/402c02af3bbb5a6e1b3015bd0caa3f0a7db618fc)
4 | See the [template](template.xml) for how to configure animations in your own mod.
5 |
6 | ## How to install
7 | 1. Install the latest build of [BepInEx](https://github.com/BepInEx/BepInEx/releases)
8 | 2. Install IllusionModdingAPI 1.39 and BepisPlugins 20.0 (or newer)
9 | 4. Download the latest release from [the releases page](../../releases)
10 | 4. Drop the dll to `bepinex/plugins`
11 | 5. Add animation packages to the mods folder. (These can usually be downloaded with KKManager)
12 |
13 | ##
14 | The manifest.xml was extended from version **1.0.8**.
15 |
16 | 1. **PositionHeroine** and **PositionPlayer** vectors (x, y, z) that represent:
17 | ```xml
18 |
19 | 0
20 | 0
21 | 0
22 |
23 |
24 | 0
25 | 0
26 | 0
27 |
28 | ```
29 | x is left and right movement (red axis)
30 | y is up and down (green axis)
31 | z is forward and backwards (blue axis)
32 | The values represent a factor by which a normalized vector in the direction of the axes is
33 | multiplied and the result added to the axis. The magnitude of normalized vector is one. The scale
34 | a good guess could be a meter. If there is a need to move half a unit the factor for that axis
35 | would be 0.5 for example.
36 |
37 | 2. **GameSpecificOverrides** - the manifest.xml is one for both KK and KKS by using this section
38 | it can be fine tuned for both games.
39 | ```xml
40 |
41 | 1
42 | Animation 1
43 | 0
44 |
45 |
46 | 55
47 |
48 |
49 |
50 | ```
51 | Here the NeckDonorId when KK reads the configuration will be 0 but when KKS reads the configuration
52 | will be 55.
53 |
54 | 3. **Game specific animations**
55 | ```xml
56 |
57 |
58 | 1
59 | Animation 1
60 | 0
61 |
62 |
63 |
64 | 2
65 | Animation 1
66 | 0
67 |
68 |
69 | ```
70 | When KKS reads this manifest the animation with StudioId 1 will be ignored. Koikatu will read both
71 | StudioId 1 and 2.
72 |
73 | 4. **Game experience levels and animations**
74 | ```xml
75 |
76 | 1
77 | Animation 1
78 | 0
79 |
80 |
81 | 55
82 | 50
83 |
84 |
85 |
86 | ```
87 | For KKS the animations are available gradually one of the factors is the heroine experience. For the
88 | game there are 3 levels None, 50%, 100%.
89 | - **None** the animation does not require experience
90 | - **50%** the animation requires at least 50% experience
91 | - **100%** the animation requires 100% experience
92 |
93 | Setting this field will apply levels to the animations. In the example above the animation will be
94 | available after the heroine reaches 50% experience.
95 |
96 | More detailed information in the
97 | [wiki](https://gitgoon.dev/IllusionMods/AnimationLoader/wiki/manifest.xml).
98 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Utils/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Diagnostics;
4 | using UnityEngine;
5 |
6 | namespace AnimationLoader
7 | {
8 | public static class Extensions
9 | {
10 | ///
11 | /// Create a coroutine that calls each of the actions in order after base coroutine finishes
12 | ///
13 | public static IEnumerator AppendCo(this IEnumerator baseCoroutine, params Action[] actions)
14 | {
15 | return new object[] { baseCoroutine, CreateCoroutine(actions) }.GetEnumerator();
16 | }
17 |
18 | ///
19 | /// Create a coroutine that calls each of the action delegates on consecutive frames
20 | /// (yield return null is returned after each one of the actions)
21 | ///
22 | public static IEnumerator CreateCoroutine(params Action[] actions)
23 | {
24 | #if DEBUG
25 | var stopWatch = new Stopwatch();
26 |
27 | stopWatch.Start();
28 | #endif
29 | foreach (var action in actions)
30 | {
31 | action();
32 | yield return null;
33 | }
34 | #if DEBUG
35 | stopWatch.Stop();
36 | var ts = stopWatch.Elapsed;
37 |
38 | var elapsedTime = string.Format("{0:00}:{1:00}:{2:00}.{3:0000}",
39 | ts.Hours, ts.Minutes, ts.Seconds,
40 | ts.Milliseconds);
41 | Log.Warning($"Load time for LoadStudioAnims {elapsedTime}");
42 | #endif
43 | }
44 |
45 | public static void SetRect(
46 | this Transform self,
47 | float anchorLeft = 0f,
48 | float anchorBottom = 0f,
49 | float anchorRight = 1f,
50 | float anchorTop = 1f,
51 | float offsetLeft = 0f,
52 | float offsetBottom = 0f,
53 | float offsetRight = 0f,
54 | float offsetTop = 0f)
55 | {
56 | var rt = (RectTransform)self;
57 | rt.anchorMin = new Vector2(anchorLeft, anchorBottom);
58 | rt.anchorMax = new Vector2(anchorRight, anchorTop);
59 | rt.offsetMin = new Vector2(offsetLeft, offsetBottom);
60 | rt.offsetMax = new Vector2(offsetRight, offsetTop);
61 | }
62 |
63 | ///
64 | /// String format Vector3 variables for easier logging
65 | ///
66 | /// Vector3 object
67 | /// decimal places in string format "F3" for example
68 | /// number of spaces left justified
69 | ///
70 | public static string Format(
71 | this Vector3 self,
72 | string decimals = default,
73 | int spaces = default)
74 | {
75 | string formatString;
76 |
77 | formatString = $"( " +
78 | $"{string.Format($"{{0,{spaces}:{decimals}}}", self.x)}, " +
79 | $"{string.Format($"{{0,{spaces}:{decimals}}}", self.y)}, " +
80 | $"{string.Format($"{{0,{spaces}:{decimals}}}", self.z)} )";
81 |
82 | return formatString;
83 | }
84 |
85 | ///
86 | /// Adjust move vector to transform vector in Unity when using transform it looks
87 | /// that moving forward sometimes is in the X axis (Why?)
88 | ///
89 | /// object self reference
90 | /// character transform
91 | ///
92 | public static Vector3 MovementTransform(this Vector3 self, Transform transform)
93 | {
94 | var result = new Vector3(0, 0, 0);
95 |
96 | result += transform.right * self.x;
97 | result += transform.up * self.y;
98 | result += transform.forward * self.z;
99 |
100 | return result;
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/LoadXML.VersionChecks.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Load XML animation information
3 | //
4 | using System;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Xml.Linq;
8 |
9 | using BepInEx.Logging;
10 |
11 |
12 | namespace AnimationLoader
13 | {
14 | public partial class SwapAnim
15 | {
16 | private static void VersionChecks(XElement manifest)
17 | {
18 | var guid = manifest?.Element("guid")?.Value;
19 | var version = manifest?.Element("version")?.Value;
20 | var author = manifest?.Element("author")?.Value;
21 | var kplugBundleVersion = manifest?.Element("KPluganimBundle")?.Value;
22 |
23 | _animationLoaderVersion = manifest?.Element("AnimationLoaderVersion");
24 |
25 | if (_animationLoaderVersion is not null)
26 | {
27 | var lines = new StringBuilder();
28 | var bundle = ".";
29 | var warning = false;
30 | var alVersion = new Version(_animationLoaderVersion.Value);
31 | var pVersion = new Version(Version);
32 |
33 | if (kplugBundleVersion != null)
34 | {
35 | var bundleVersion = BundleVersion();
36 | var minVersion = new Version(kplugBundleVersion);
37 | if (bundleVersion != null)
38 | {
39 | if (bundleVersion.CompareTo(minVersion) < 0)
40 | {
41 | bundle = $" KPlug Animation Bundle " +
42 | $"version={bundleVersion} minimum={minVersion} some " +
43 | $"features may not work upgrade to latest version.";
44 | warning = true;
45 | }
46 | else
47 | {
48 | bundle = $" KPlug Animation Bundle version={bundleVersion} " +
49 | $"minimum={minVersion}.";
50 | }
51 | }
52 | }
53 | if (pVersion != null)
54 | {
55 | if (pVersion.CompareTo(alVersion) < 0)
56 | {
57 | var tmp = author is not null ? author : "N/A";
58 | lines.AppendLine($"0011: Manifest " +
59 | $"guid={guid} version={version} author=[{tmp}] " +
60 | $"AnimationLoader version={pVersion} minimum={alVersion} " +
61 | $"some features may not work upgrade to latest version,{bundle}");
62 | warning = true;
63 | }
64 | else
65 | {
66 | var tmp = author is not null ? author : "N/A";
67 | lines.AppendLine($"0011: Manifest " +
68 | $"guid={guid} version={version} author=[{tmp}] " +
69 | $"AnimationLoader version={pVersion} minimum={alVersion}" +
70 | $"{bundle}");
71 | }
72 | }
73 | if (warning)
74 | {
75 | Log.Level(LogLevel.Warning | LogLevel.Debug, lines.ToString());
76 | }
77 | else
78 | {
79 | Log.Level(LogLevel.Info | LogLevel.Debug, lines.ToString());
80 | }
81 | }
82 | }
83 |
84 | private static Version BundleVersion()
85 | {
86 | var manifests = Sideloader.Sideloader.Manifests.Values
87 | .Select(x => x.ManifestDocument);
88 | var manifest = manifests
89 | .Select(x => x.Root)
90 | .Where(x => x?.Element("guid").Value == "kpluganim.bundle")
91 | .FirstOrDefault();
92 |
93 | if (manifest != null)
94 | {
95 | return new Version(manifest.Element("version").Value);
96 | }
97 | return null;
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Fixes/Move.Controller.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using UnityEngine;
4 |
5 | using BepInEx.Logging;
6 |
7 | using KKAPI;
8 | using KKAPI.Chara;
9 |
10 |
11 | namespace AnimationLoader
12 | {
13 | public partial class SwapAnim
14 | {
15 | ///
16 | /// Move characters to a different positions
17 | ///
18 | public partial class MoveController : CharaCustomFunctionController
19 | {
20 | internal Vector3 _originalPosition = new(0, 0, 0);
21 | internal Vector3 _lastMovePosition = new(0, 0, 0);
22 | internal CharacterType _chaType = CharacterType.Unknown;
23 |
24 | public enum CharacterType { Heroine, Heroine3P, Player, Janitor, Group, Unknown }
25 |
26 | ///
27 | /// Required definition.
28 | ///
29 | ///
30 | protected override void OnCardBeingSaved(GameMode currentGameMode)
31 | {
32 | }
33 |
34 | internal void Init(CharacterType chaType)
35 | {
36 | _chaType = chaType;
37 | }
38 |
39 | ///
40 | /// Save original position
41 | ///
42 | internal void SetOriginalPosition(Vector3 position)
43 | {
44 | _originalPosition = position;
45 | }
46 |
47 | ///
48 | /// Restore original position
49 | ///
50 | public void ResetPosition()
51 | {
52 | ChaControl.transform.position = _originalPosition;
53 | #if DEBUG
54 | Log.Warning($"0032: Resetting character position for {_chaType}" +
55 | $"to position={ChaControl.transform.position.Format()}.");
56 | #endif
57 | }
58 |
59 | public void Move(Vector3 move)
60 | {
61 | try
62 | {
63 | //if (_originalPosition == Vector3.zero)
64 | //{
65 | // originalPosition = character.transform.position;
66 | //}
67 | var original = ChaControl.transform.position;
68 | var rightZAxis = ChaControl.transform.right * move.x;
69 | var upYAxis = ChaControl.transform.up * move.y;
70 | var upXAxis = ChaControl.transform.forward * move.z;
71 |
72 | // In manifest x is right z is forward
73 | // in game transform x is forward z is right
74 | Vector3 gameMove = new(move.z, move.y, move.x);
75 | var newPosition = original + gameMove;
76 | var gameMove2 = move.MovementTransform(ChaControl.transform);
77 |
78 | ChaControl.transform.position += upXAxis;
79 | ChaControl.transform.position += upYAxis;
80 | ChaControl.transform.position += rightZAxis;
81 | _lastMovePosition = ChaControl.transform.position;
82 | #if DEBUG
83 | Log.Level(LogLevel.Warning,
84 | $"[Move] Adjusting character position for {_chaType}\n" +
85 | $" move={gameMove.Format()}\n" +
86 | $" trans move={gameMove2.Format()}\n" +
87 | $" up={ChaControl.transform.up.Format()}\n" +
88 | $" right={ChaControl.transform.right.Format()}\n" +
89 | $" forward={ChaControl.transform.forward.Format()}\n" +
90 | $" y(up)={upYAxis.Format()}\n" +
91 | $" z(right)={rightZAxis.Format()}\n" +
92 | $" x(forward)={upXAxis.Format()}\n" +
93 | $" From={original.Format()}\n" +
94 | $" to={_lastMovePosition.Format()}\n" +
95 | $" vector+={newPosition.Format()}.");
96 | #else
97 | Log.Debug($"0031: Adjusting character position for {_chaType} to " +
98 | $"position={_lastMovePosition.Format()}.");
99 | #endif
100 | }
101 | catch (Exception e)
102 | {
103 | Log.Level(LogLevel.Error, $"0010: Cannot adjust {_chaType} - " +
104 | $"{ChaControl.name} - {e}.");
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Hooks.cs:
--------------------------------------------------------------------------------
1 | //
2 | // AnimationLoader Hooks
3 | //
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Reflection.Emit;
7 |
8 | using H;
9 |
10 | using HarmonyLib;
11 |
12 | using static AnimationLoader.SwapAnim.MoveController;
13 |
14 |
15 | namespace AnimationLoader
16 | {
17 | public partial class SwapAnim
18 | {
19 | private static ChaControl _heroine;
20 | private static ChaControl _heroine3P;
21 | private static List _lstHeroines;
22 | private static ChaControl _player;
23 | private static HFlag _flags;
24 | private static Harmony _hookInstance;
25 | private static bool _specialAnimation = false;
26 |
27 | internal partial class Hooks
28 | {
29 | ///
30 | /// Initialize the Hooks patch instance
31 | ///
32 | internal static void Init()
33 | {
34 | _hookInstance = Harmony.CreateAndPatchAll(typeof(Hooks));
35 |
36 | if (VRHSceneType != null)
37 | {
38 | _hookInstance.Patch(
39 | AccessTools.Method(
40 | VRHSceneType,
41 | nameof(HSceneProc.ChangeAnimator)),
42 | postfix: new HarmonyMethod(typeof(Hooks),
43 | nameof(SwapAnimation)));
44 | _hookInstance.Patch(
45 | AccessTools.Method(
46 | VRHSceneType,
47 | nameof(HSceneProc.CreateAllAnimationList)),
48 | postfix: new HarmonyMethod(typeof(Hooks),
49 | nameof(ExtendList)));
50 | }
51 | }
52 |
53 | ///
54 | /// Initialize MoveController for characters
55 | ///
56 | ///
57 | ///
58 | ///
59 | [HarmonyPrefix]
60 | [HarmonyPatch(typeof(HSceneProc), nameof(HSceneProc.SetShortcutKey))]
61 | private static void SetShortcutKeyPrefix(
62 | object __instance,
63 | List ___lstFemale,
64 | ChaControl ___male)
65 | {
66 | _lstHeroines = ___lstFemale;
67 | _heroine = _lstHeroines[0];
68 | GetMoveController(_heroine).Init(CharacterType.Heroine);
69 |
70 | if (___lstFemale.Count > 1)
71 | {
72 | _heroine3P = _lstHeroines[1];
73 | GetMoveController(_heroine3P).Init(CharacterType.Heroine3P);
74 | }
75 |
76 | _player = ___male;
77 | GetMoveController(_player).Init(CharacterType.Player);
78 |
79 | var hsceneTraverse = Traverse.Create(__instance);
80 | var categorys = hsceneTraverse.Field>("categorys").Value;
81 | _specialAnimation = (categorys[0] is 12 or > 1000);
82 |
83 | _flags = hsceneTraverse
84 | .Field("flags").Value;
85 | }
86 |
87 | [HarmonyTranspiler]
88 | [HarmonyPatch(typeof(HSprite), nameof(HSprite.OnChangePlaySelect))]
89 | private static IEnumerable OnChangePlaySelect(
90 | IEnumerable instructions)
91 | {
92 | // Force position change even if position appears to match.
93 | // Prevents clicks from being eaten.
94 | return new CodeMatcher(instructions)
95 | .MatchForward(false,
96 | new CodeMatch(
97 | OpCodes.Ldfld,
98 | AccessTools.Field(typeof(HSprite),
99 | "flags")),
100 | new CodeMatch(
101 | OpCodes.Ldfld,
102 | AccessTools.Field(typeof(HFlag),
103 | "nowAnimationInfo")),
104 | new CodeMatch(
105 | OpCodes.Ldfld,
106 | AccessTools.Field(typeof(HSceneProc.AnimationListInfo),
107 | "id"))
108 | )
109 | .MatchForward(false,
110 | new CodeMatch(OpCodes.Ret)
111 | )
112 | .SetAndAdvance(OpCodes.Nop, null)
113 | .InstructionEnumeration();
114 | }
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/AnimationLoader.KoikatsuSunshine/LoadXML.Overrides.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Load XML animation information
3 | //
4 | using UnityEngine;
5 |
6 |
7 | namespace AnimationLoader
8 | {
9 | public partial class SwapAnim
10 | {
11 | private static void Override(ref string lhs, string rhs)
12 | {
13 | if (rhs != null)
14 | {
15 | lhs = rhs;
16 | }
17 | }
18 |
19 | private static void Override(ref int lhs, int rhs)
20 | {
21 | if (rhs >= 0)
22 | {
23 | lhs = rhs;
24 | }
25 | }
26 |
27 | private static void DoOverrides(
28 | ref SwapAnimationInfo data,
29 | OverrideInfo overrides,
30 | ref Animation animation,
31 | bool overrideName)
32 | {
33 | if (overrides.PathFemale != null)
34 | {
35 | data.PathFemale = string.Copy(overrides.PathFemale);
36 | }
37 | if (overrides.ControllerFemale != null)
38 | {
39 | data.ControllerFemale = string.Copy(overrides.ControllerFemale);
40 | }
41 |
42 | Override(ref data.PathFemale1, overrides.PathFemale1);
43 | Override(ref data.ControllerFemale1, overrides.ControllerFemale1);
44 |
45 | if (overrides.PathMale != null)
46 | {
47 | data.PathMale = string.Copy(overrides.PathMale);
48 | }
49 | if (overrides.ControllerMale != null)
50 | {
51 | data.ControllerMale = string.Copy(overrides.ControllerMale);
52 | }
53 | if ((overrides.AnimationName != null) && !overrideName)
54 | {
55 | data.AnimationName = string.Copy(overrides.AnimationName);
56 | if (UserOverrides.Value)
57 | {
58 | animation.KoikatsuSunshine = string.Copy(overrides.AnimationName);
59 | animation.KoikatsuSunshineReference = string
60 | .Copy(overrides.AnimationName);
61 | }
62 | }
63 | if (overrides.Mode >= 0)
64 | {
65 | data.Mode = overrides.Mode;
66 | }
67 | if (overrides.kindHoushi >= 0)
68 | {
69 | data.kindHoushi = overrides.kindHoushi;
70 | }
71 |
72 | overrides.categories?.CopyTo(data.categories, 0);
73 |
74 | if (overrides.DonorPoseId >= 0)
75 | {
76 | data.DonorPoseId = overrides.DonorPoseId;
77 | }
78 |
79 | if (overrides.NeckDonorId >= -1)
80 | {
81 | data.NeckDonorId = overrides.NeckDonorId;
82 | }
83 |
84 | Override(ref data.NeckDonorIdFemale, overrides.NeckDonorIdFemale);
85 | Override(ref data.NeckDonorIdFemale1, overrides.NeckDonorIdFemale1);
86 |
87 | if (overrides.NeckDonorIdMale >= 0)
88 | {
89 | data.NeckDonorIdMale = overrides.NeckDonorIdMale;
90 | }
91 |
92 | if (overrides.FileMotionNeck != null)
93 | {
94 | data.FileMotionNeck = string.Copy(overrides.FileMotionNeck);
95 | }
96 | if (overrides.FileMotionNeckMale != null)
97 | {
98 | data.FileMotionNeckMale = string.Copy(overrides.FileMotionNeckMale);
99 | }
100 | if (overrides.IsFemaleInitiative != null)
101 | {
102 | data.IsFemaleInitiative = overrides.IsFemaleInitiative;
103 | }
104 | if (overrides.FileSiruPaste != null)
105 | {
106 | data.FileSiruPaste = string.Copy(overrides.FileSiruPaste);
107 | }
108 | if (overrides.MotionIKDonor != null)
109 | {
110 | data.MotionIKDonor = overrides.MotionIKDonor;
111 | }
112 | if (overrides.MotionIKDonorFemale != null)
113 | {
114 | data.MotionIKDonorFemale = overrides.MotionIKDonorFemale;
115 | }
116 | if (overrides.MotionIKDonorFemale1 != null)
117 | {
118 | data.MotionIKDonorFemale1 = overrides.MotionIKDonorFemale1;
119 | }
120 | if (overrides.MotionIKDonorMale != null)
121 | {
122 | data.MotionIKDonorMale = overrides.MotionIKDonorMale;
123 | }
124 | if (overrides.ExpTaii >= 0)
125 | {
126 | data.ExpTaii = overrides.ExpTaii;
127 | }
128 | if (overrides.PositionHeroine != Vector3.zero)
129 | {
130 | data.PositionHeroine = overrides.PositionHeroine;
131 | }
132 | if (overrides.PositionPlayer != Vector3.zero)
133 | {
134 | data.PositionPlayer = overrides.PositionPlayer;
135 | }
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/LoadXML.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Load XML animation information
3 | //
4 | //using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Xml.Linq;
10 | using System.Xml.Serialization;
11 |
12 | using BepInEx;
13 | using BepInEx.Logging;
14 | using KKAPI;
15 |
16 | //using UnityEngine;
17 | using static HFlag;
18 |
19 |
20 | namespace AnimationLoader
21 | {
22 | public partial class SwapAnim
23 | {
24 | private const string ManifestRootElement = "AnimationLoader";
25 | private const string ManifestArrayItem = "Animation";
26 | private const string ManifestGSArrayItem = KoikatuAPI.GameProcessName;
27 |
28 | private static readonly XmlSerializer xmlSerializer =
29 | new(typeof(SwapAnimationInfo));
30 | #if KKS
31 | private const string ManifestOverride = "GameSpecificOverrides";
32 | private static readonly XmlSerializer xmlOverrideSerializer =
33 | new(typeof(OverrideInfo));
34 | #endif
35 | private static XElement _animRoot;
36 | private static XElement _animRootGS;
37 | private static XElement _animationLoaderVersion;
38 |
39 | private static bool _saveNames = false;
40 |
41 | private static void LoadTestXml()
42 | {
43 | var path = Path.Combine(Paths.ConfigPath, "AnimationLoader");
44 | if(Directory.Exists(path))
45 | {
46 | var docs = Directory.GetFiles(path, "*.xml")
47 | .Select(XDocument.Load).ToList();
48 | if(docs.Count > 0)
49 | {
50 | Log.Level(LogLevel.Message, $"0014: [{PluginName}] Loading test " +
51 | $"animations");
52 | LoadXml(docs);
53 | return;
54 | }
55 | }
56 | Log.Level(LogLevel.Message, "0015: Make a manifest format .xml in the " +
57 | "config/AnimationLoader folder to test animations");
58 | }
59 |
60 | private static void LoadXml(IEnumerable manifests)
61 | {
62 | animationDict = [];
63 | var count = 0;
64 | var overrideNames = UserOverrides.Value;
65 | var logLines = new StringBuilder();
66 |
67 | // Select the only manifests that AnimationLoader will process
68 | foreach (var manifest in manifests
69 | .Select(x => x.Root)
70 | .Where(x => x?.Element(ManifestRootElement) != null))
71 | {
72 | _animRoot = manifest?.Element(ManifestRootElement);
73 |
74 | _animRootGS = manifest?
75 | .Element(ManifestRootElement)?
76 | .Element(KoikatuAPI.GameProcessName);
77 |
78 | if ((_animRoot is null) && (_animRootGS is null))
79 | {
80 | continue;
81 | }
82 | var guid = manifest?.Element("guid").Value;
83 | var version = manifest?.Element("version").Value;
84 |
85 | VersionChecks(manifest);
86 |
87 | // Process elements valid for any game
88 | count += ProcessArray(
89 | _animRoot, guid, version, overrideNames, ref logLines);
90 | if (_animRootGS is null)
91 | {
92 | if (_saveNames)
93 | {
94 | SaveNames(animationNamesDict[guid], guid, true);
95 | }
96 | continue;
97 | }
98 |
99 | // Process game specific animations
100 | // Not needed from 1.5.3 on.
101 | var _animSpecific = _animRoot?.Elements(ManifestGSArrayItem);
102 |
103 | if (_animSpecific is not null)
104 | {
105 | foreach (var gameSpecificElement in _animSpecific)
106 | {
107 | if (gameSpecificElement is null)
108 | {
109 | continue;
110 | }
111 | count += ProcessArray(
112 | gameSpecificElement,
113 | guid,
114 | version,
115 | overrideNames,
116 | ref logLines);
117 | }
118 | }
119 |
120 | if (_saveNames)
121 | {
122 | SaveNames(animationNamesDict[guid], guid, true);
123 | }
124 | }
125 | if (count > 0)
126 | {
127 | #if KKS
128 | Utilities.AlDicExpAddTaii();
129 | #endif
130 | logLines.Append($"A total of {count} animations processed from " +
131 | "manifests.");
132 | Log.Debug($"0016: Animations loaded:\n\n{logLines}\n");
133 | }
134 | else
135 | {
136 | Log.Level(
137 | LogLevel.Message | LogLevel.Debug,
138 | "0017: No animation manifests found.");
139 | }
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | #### v1.1.3.4 - 2024-03-16
4 |
5 | ##### Added
6 |
7 | - If the animation is a foot job the heroine shoes will be removed automatically
8 |
9 | #### v1.1.2.0 - 2022-06-16
10 |
11 | ##### Added
12 |
13 | - 3P support
14 | - Manifest will have the minimum version needed of AnimationLoader
15 | - If the version needed is higher a warning massage will be logged
16 |
17 | ##### Changed
18 |
19 | - NeckDonorId option in manifest can be configured per character in the animation
20 | - The NeckDonorId does not have to be the same for every character. For certain animations different NeckDonorId's works better
21 |
22 | #### v1.1.1.3 - 2022-04-19
23 |
24 | Update libraries used.
25 |
26 | ##### Added
27 |
28 | - [KKS] Really basic MotionIK support for a few cases that the donor animations more or less align
29 |
30 | #### v1.1.1.1 - 2022-02-12
31 |
32 | ##### Fixed
33 |
34 | - [KKS] Fix problem in KKS when disabling the plug-in in Character Studio.
35 |
36 | #### v1.1.1 - 2022-02-12
37 |
38 | ##### Added
39 |
40 | - [KKS] VR support
41 | - [KKS] New labels for added animations work as expected
42 | - [KKS] In Free-H if added animations are New (unused) they are not available like the standard
43 | ones. Optional disable in configuration.
44 | - [KKS] Animations can be made dependent of experience like the standard ones.
45 | Optional disable in configuration.
46 | - Animation names can be maintained by the user. Names are saved in UserData/AnimationLoader/Names.
47 | There will be one file per animation bundle. Off by default.
48 |
49 | ##### Changed
50 |
51 | - Optimizations to reduce the load time in Character Studio
52 |
53 | ##### Fixed
54 |
55 | - [KK] Fixed button duplication in Grid
56 | - [@Kokaiinum] fix exception when setting NeckDonorId
57 |
58 | When posting issues one thing that may help to find a solution faster is turning on Debug Information (Advance Settings) and post the output_log.txt in [GitHub](https://gitgoon.dev/IllusionMods/AnimationLoader/issues). If you post it only on Discord and don't address IDontHaveIdea I may not see it.
59 |
60 |
61 |
62 | #### v1.1.0.4b1 - 2022-02-08
63 |
64 | ##### Added
65 |
66 | ##### Changed
67 |
68 | - Optimizations to reduce the load time in Character Studio
69 |
70 | ##### Fixed
71 |
72 |
73 | #### v1.1.0.3b1 - 2022-02-05
74 |
75 | Only one dll for standard and VR in KoikatsuSunshine. It needs KKAPI 1.31.2 minimum.
76 |
77 | ##### Added
78 |
79 | ##### Changed
80 |
81 | - [KKS] One dll for standard and VR modes
82 |
83 | ##### Fixed
84 |
85 | - [KKS] Configuration for FreeH animations was missing.
86 | - [KKS] Change List to HashSet for used animations tracking any duplicate entries will be safely
87 | removed
88 |
89 | ### v1.1.0.1b1 - 2022-02-03
90 |
91 | ##### Added
92 |
93 | - The majority of logs are enabled in config
94 | - Log warning when moving characters
95 | - [KKS] New labels for added animations work as expected
96 | - [KKS] In Free-H if added animations are New they are not available like the standard ones
97 | - [KKS] Animations can be made dependent of experience.
98 | - Animation names can be maintained by the user. (Off by default)
99 |
100 | ##### Changed
101 |
102 | - Log of lists are done in one call
103 | - Reorganize the project renaming and major refactoring
104 | - [KKS] LoadMotionList re-done complete code for buttons in order to ease extensions to plug-in
105 |
106 | ##### Fixed
107 |
108 | - [KK] Fixed button duplication in Grid
109 | - [@Kokaiinum] fix exception when setting NeckDonorId
110 |
111 | ### v1.1.0.0 - 2021-12-25
112 |
113 | First official release with support for Koikatsu Sunshine.
114 |
115 | ##### Added
116 |
117 | - Option to adjust character positions
118 | - Support manifest extension in KK
119 | - Specified processes permitted to load in KK
120 | - Enable two more animations that needed position adjustment
121 | - For developers if there no zipmod with a manifest for animations it will load any test
122 | manifests found in the directory config\AnimationLoader
123 | - Animations can be mark individually as game specific no need to group them all in one section
124 | - Mod can be disabled for Studio if not needed
125 |
126 | ##### Changed
127 |
128 | - Modified the manifest.xml for katarsys animations using new extensions
129 | - SiruPasteFiles can be specified by the name no need to be in dictionary. The dictionary is still
130 | used it has precedence.
131 | - Updates to manifest.xml for KKS (changes in some donors id's)
132 | - Update to latest libraries in KK
133 |
134 | ##### Fixed
135 |
136 | - kind of hoshi of the donor was used instead of the one defined in the manifest
137 |
138 | ### v1.0.9b1 - 2021-12-10
139 |
140 | This is the first release with Koikatsu Sunshine support. In KKS the animations are available
141 | depending on the Heroine experience.a
142 |
143 | ##### Added
144 |
145 | - Koikatsu Sunshine Support
146 | - Extensions to the manifest.xml format
147 |
148 | ##### Changed
149 |
150 | - Solution use shared code now
151 |
152 |
153 | ###### Known Issues
154 |
155 | - For the game the experience has three levels 0-49, 50-99 and a 100. Dependency in experience can
156 | be increased that is if a donor has 100% experience requirement it can not be lowered. If the
157 | experience is level 2 (50-99) it can go level 3 for example. For added animations is dot not have to
158 | be 3 levels.
159 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Plugin.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | #if DEBUG
5 | using System.Diagnostics;
6 | #endif
7 |
8 | using BepInEx.Logging;
9 |
10 | using KKAPI;
11 | using KKAPI.Chara;
12 | using KKAPI.MainGame;
13 |
14 | #if KK
15 | using UnityEngine;
16 | #endif
17 |
18 | using static HFlag;
19 |
20 |
21 | namespace AnimationLoader
22 | {
23 | public partial class SwapAnim
24 | {
25 | #if KK
26 | private static readonly Color buttonColor = new(0.96f, 1f, 0.9f);
27 | #endif
28 | private static Dictionary> animationDict;
29 | private static Dictionary
30 | swapAnimationMapping;
31 | private static readonly Type VRHSceneType = Type.GetType("VRHScene, Assembly-CSharp");
32 |
33 | private static readonly Dictionary SiruPasteFiles = new() {
34 | { "", "" },
35 | { "butt", "siru_t_khs_n06" },
36 | { "facetits", "siru_t_khh_32" },
37 | { "facetitspussy", "siru_t_khh_32" }, // have to make this manually, for now copy FaceTits
38 | { "titspussy", "siru_t_khs_n07" },
39 | { "tits", "siru_t_khh_11" },
40 | { "pussy", "siru_t_khs_n07" }, // have to make this manually, for now copy TitsPussy
41 | { "kksbutt", "siru_t_khs_14" },
42 | { "kksfacetits", "siru_t_khh_33" },
43 | };
44 |
45 | private static readonly Dictionary EModeGroups = new() {
46 | { "aibu1", 998 },
47 | { "houshi0", 999 },
48 | { "houshi1", 1000 },
49 | { "sonyu0", 1001 },
50 | { "sonyu1", 1002 },
51 | { "masturbation1", 1003 },
52 | { "peeping0", 1004 },
53 | { "peeping1", 1005 },
54 | { "lesbian1", 1006 },
55 | };
56 |
57 | ///
58 | /// Make information available for other plugins
59 | ///
60 | public static Dictionary> AnimationsDict {
61 | get { return animationDict; }
62 | private set { animationDict = value; }
63 | }
64 |
65 | ///
66 | /// Make information available for other plugins
67 | ///
68 | public static Dictionary
69 | SwapAnimationMapping {
70 | get { return swapAnimationMapping; }
71 | private set { swapAnimationMapping = value; }
72 | }
73 |
74 | private void Awake()
75 | {
76 | Log.LogSource = Logger; ;
77 |
78 | ConfigEntries();
79 |
80 | Log.Enabled = DebugInfo.Value;
81 | Log.DebugToConsole = DebugToConsole.Value;
82 | #if KKS
83 | if (KoikatuAPI.GetCurrentGameMode() == GameMode.Studio)
84 | {
85 | if (!LoadInCharStudio.Value)
86 | {
87 | Log.Level(LogLevel.Message, "0013: Animation Loader disabled in configuration.");
88 | enabled = false;
89 | return;
90 | }
91 | }
92 | #endif
93 | Hooks.Init();
94 | // Register move characters controller
95 | CharacterApi.RegisterExtraBehaviour(GUID);
96 | #if KKS
97 | // To save used animations on H exit
98 | GameAPI.RegisterExtraBehaviour(GUID);
99 | #endif
100 | }
101 |
102 | private void Start()
103 | {
104 | #if DEBUG
105 | var stopWatch = new Stopwatch();
106 |
107 | stopWatch.Start();
108 | #endif
109 | #if KKS
110 | // Read used animations
111 | _usedAnimations.Read();
112 | _animationsUseStats.Read();
113 | #endif
114 | //
115 | // Save names for animations for users who update them and not overwritten
116 | // with updates
117 | //
118 | LoadNamesXml();
119 |
120 | //
121 | // Get loaded manifests
122 | //
123 | LoadXml(Sideloader.Sideloader.Manifests.Values.Select(x => x.ManifestDocument));
124 |
125 | //
126 | // Read foot job animations
127 | //
128 | _footJobAnimations.Read();
129 | #if DEBUG
130 | stopWatch.Stop();
131 | var ts = stopWatch.Elapsed;
132 |
133 | var elapsedTime = string.Format("{0:00}:{1:00}:{2:00}.{3:00000}",
134 | ts.Hours, ts.Minutes, ts.Seconds,
135 | ts.Milliseconds);
136 | Log.Level(LogLevel.Warning, $"Load time for LoadXmls {elapsedTime}");
137 |
138 | //
139 | // For test environment animations manifest are kept in config/AnimationLoader
140 | // when the plug-in starts it will load them if no zipmod with manifests found
141 | // May be a feature config flag for everybody or load from here not from there??
142 | // I like the last one.
143 | //
144 | if (animationDict.Count < 1)
145 | {
146 | stopWatch.Reset();
147 | stopWatch.Start();
148 |
149 | LoadTestXml();
150 |
151 | stopWatch.Stop();
152 | ts = stopWatch.Elapsed;
153 | elapsedTime = string.Format("{0:00}:{1:00}:{2:00}.{3:00000}",
154 | ts.Hours, ts.Minutes, ts.Seconds,
155 | ts.Milliseconds);
156 | Log.Level(LogLevel.Warning, $"Load time for LoadXmls {elapsedTime}");
157 | }
158 | #endif
159 | }
160 |
161 | private void Update()
162 | {
163 | if (ReloadManifests.Value.IsDown())
164 | {
165 | LoadTestXml();
166 | _footJobAnimations.Read();
167 | }
168 | }
169 |
170 | ///
171 | /// Get move controller for characters
172 | ///
173 | ///
174 | ///
175 | public static MoveController GetMoveController(ChaControl chaControl) =>
176 | (chaControl == null) || (chaControl.gameObject == null)
177 | ? null : chaControl.GetComponent();
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/SwapAnimationInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Xml.Linq;
3 | using System.Xml.Serialization;
4 |
5 | using UnityEngine;
6 |
7 | using KKAPI;
8 |
9 | using static HFlag;
10 |
11 |
12 | namespace AnimationLoader
13 | {
14 | [XmlRoot("Animation")]
15 | [Serializable]
16 | public class SwapAnimationInfo
17 | {
18 | private bool _isRelease = false;
19 |
20 | [XmlIgnore]
21 | public string Guid;
22 |
23 | [XmlElement]
24 | public int StudioId = -1;
25 |
26 | [XmlElement]
27 | public string PathFemale;
28 |
29 | [XmlElement]
30 | public string ControllerFemale;
31 |
32 | [XmlElement]
33 | public string PathFemale1;
34 |
35 | [XmlElement]
36 | public string ControllerFemale1;
37 |
38 | [XmlElement]
39 | public string PathMale;
40 |
41 | [XmlElement]
42 | public string ControllerMale;
43 |
44 | [XmlElement]
45 | public string AnimationName;
46 |
47 | [XmlElement]
48 | public HFlag.EMode Mode;
49 |
50 | [XmlElement]
51 | public KindHoushi kindHoushi;
52 |
53 | [XmlArray]
54 | [XmlArrayItem("category", Type = typeof(PositionCategory))]
55 | public PositionCategory[] categories = [];
56 | //public PositionCategory[] categories = new PositionCategory[0];
57 |
58 | [XmlElement]
59 | public int DonorPoseId = -1;
60 |
61 | // NeckDonorId set to -2 so it can be overwritten in manifest when needed
62 | [XmlElement]
63 | public int NeckDonorId = -2;
64 |
65 | [XmlElement]
66 | public int NeckDonorIdFemale = -1;
67 |
68 | [XmlElement]
69 | public int NeckDonorIdFemale1 = -1;
70 |
71 | [XmlElement]
72 | public int NeckDonorIdMale = -1;
73 |
74 | [XmlElement]
75 | public string FileMotionNeck;
76 |
77 | [XmlElement]
78 | public string FileMotionNeckMale;
79 |
80 | [XmlElement]
81 | public bool? IsFemaleInitiative;
82 |
83 | [XmlElement]
84 | public string FileSiruPaste;
85 |
86 | [XmlElement]
87 | public string MotionIKDonor;
88 |
89 | [XmlElement]
90 | public string MotionIKDonorFemale;
91 |
92 | [XmlElement]
93 | public string MotionIKDonorFemale1;
94 |
95 | [XmlElement]
96 | public string MotionIKDonorMale;
97 |
98 | [XmlElement]
99 | public int ExpTaii = -1;
100 |
101 | [XmlElement]
102 | public Vector3 PositionHeroine = Vector3.zero;
103 |
104 | [XmlElement]
105 | public Vector3 PositionPlayer = Vector3.zero;
106 |
107 | [XmlElement]
108 | public string SpecificFor = string.Empty;
109 |
110 | public bool IsRelease {
111 | get
112 | {
113 | return _isRelease;
114 | }
115 | set
116 | {
117 | _isRelease = value;
118 | }
119 | }
120 | }
121 |
122 | [XmlRoot(KoikatuAPI.GameProcessName)]
123 | [Serializable]
124 | public class OverrideInfo
125 | {
126 | [XmlIgnore]
127 | public string Guid;
128 |
129 | [XmlElement]
130 | public int StudioId = -1;
131 |
132 | [XmlElement]
133 | public string PathFemale;
134 |
135 | [XmlElement]
136 | public string ControllerFemale;
137 |
138 | [XmlElement]
139 | public string PathFemale1;
140 |
141 | [XmlElement]
142 | public string ControllerFemale1;
143 |
144 | [XmlElement]
145 | public string PathMale;
146 |
147 | [XmlElement]
148 | public string ControllerMale;
149 |
150 | [XmlElement]
151 | public string AnimationName;
152 |
153 | [XmlElement]
154 | public EMode Mode = EMode.none;
155 |
156 | [XmlElement]
157 | public KindHoushi kindHoushi = KindHoushi.none;
158 |
159 | [XmlArray]
160 | [XmlArrayItem("category", Type = typeof(PositionCategory))]
161 | //public PositionCategory[] categories = new PositionCategory[0];
162 | public PositionCategory[] categories = [];
163 |
164 | [XmlElement]
165 | public int DonorPoseId = -1;
166 |
167 | // NeckDonorId set to -2 so it can be overwritten in manifest when needed
168 | [XmlElement]
169 | public int NeckDonorId = -2;
170 |
171 | [XmlElement]
172 | public int NeckDonorIdFemale = -1;
173 |
174 | [XmlElement]
175 | public int NeckDonorIdFemale1 = -1;
176 |
177 | [XmlElement]
178 | public int NeckDonorIdMale = -1;
179 |
180 | [XmlElement]
181 | public string FileMotionNeck;
182 |
183 | [XmlElement]
184 | public string FileMotionNeckMale;
185 |
186 | [XmlElement]
187 | public bool? IsFemaleInitiative;
188 |
189 | [XmlElement]
190 | public string FileSiruPaste;
191 |
192 | [XmlElement]
193 | public string MotionIKDonor;
194 |
195 | [XmlElement]
196 | public string MotionIKDonorFemale;
197 |
198 | [XmlElement]
199 | public string MotionIKDonorFemale1;
200 |
201 | [XmlElement]
202 | public string MotionIKDonorMale;
203 |
204 | [XmlElement]
205 | public int ExpTaii = -1;
206 |
207 | [XmlElement]
208 | public Vector3 PositionHeroine = Vector3.zero;
209 |
210 | [XmlElement]
211 | public Vector3 PositionPlayer = Vector3.zero;
212 | }
213 |
214 | public enum KindHoushi
215 | {
216 | Hand = 0,
217 | Mouth = 1,
218 | Breasts = 2,
219 | none = -1
220 | }
221 |
222 | public enum PositionCategory
223 | {
224 | LieDown = 0,
225 | Stand = 1,
226 | SitChair = 2,
227 | Stool = 3,
228 | SofaBench = 4,
229 | BacklessBench = 5,
230 | SchoolDesk = 6,
231 | Desk = 7,
232 | Wall = 8,
233 | StandPool = 9,
234 | SitDesk = 10,
235 | SquadDesk = 11,
236 | Pool = 1004,
237 | MischievousCaress = 1003,
238 | LieDownLesbian = 1100,
239 | SitChairLesbian = 1101,
240 | StandLesbian = 1102,
241 | ALLesbian50 = 1150,
242 | AquariumCrowded = 1304,
243 | LieDown3P = 3000,
244 | SitChair3P = 3001
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/LoadXML.ProcessArray.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Load XML animation information
3 | //
4 | using System;
5 | using System.Collections.Generic;
6 | //using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Xml.Linq;
10 | //using System.Xml.Serialization;
11 |
12 | //using BepInEx;
13 | //using BepInEx.Logging;
14 |
15 | using KKAPI;
16 |
17 | //using UnityEngine;
18 |
19 | //using static HFlag;
20 |
21 |
22 | namespace AnimationLoader
23 | {
24 | public partial class SwapAnim
25 | {
26 | private static int ProcessArray(
27 | XElement root,
28 | string guid,
29 | string version,
30 | bool overrideNames,
31 | ref StringBuilder logLines)
32 | {
33 | var count = 0;
34 | var specificLog = new StringBuilder();
35 | var specificFor = false;
36 |
37 | if (Sideloader.Sideloader.ZipArchives.TryGetValue(guid, out var zipFileName))
38 | {
39 | logLines.Append($"From {zipFileName} {guid}-{version}: " +
40 | $"{RootText(root.Name)}\n");
41 | }
42 | else
43 | {
44 | logLines.Append($"From {guid}-{version}: {RootText(root.Name)}\n");
45 | }
46 | foreach (var animElem in root.Elements(ManifestArrayItem))
47 | {
48 | if (animElem == null)
49 | {
50 | continue;
51 | }
52 | var animation = new Animation();
53 | var reader = animElem.CreateReader();
54 | var overrideName = overrideNames;
55 | var data = (SwapAnimationInfo)xmlSerializer.Deserialize(reader);
56 | data.Guid = guid;
57 | reader.Close();
58 |
59 | if (!data.SpecificFor.IsNullOrWhiteSpace())
60 | {
61 | if (data.SpecificFor.Equals(KoikatuAPI.GameProcessName))
62 | {
63 | specificFor = true;
64 | }
65 | else
66 | {
67 | // Not for current game
68 | continue;
69 | }
70 | }
71 |
72 | if (UserOverrides.Value)
73 | {
74 | // user override name
75 | animation.StudioId = data.StudioId;
76 | animation.Controller = string.Copy(data.ControllerFemale);
77 | animation.Koikatu = string.Copy(data.AnimationName);
78 | animation.KoikatuReference = string.Copy(data.AnimationName);
79 | animation.KoikatsuSunshine = string.Copy(data.AnimationName);
80 | animation.KoikatsuSunshineReference = string
81 | .Copy(data.AnimationName);
82 |
83 | overrideName = false;
84 | var animationOverride = new Animation();
85 |
86 | if (animationNamesDict.TryGetValue(guid, out var names))
87 | {
88 | if (names.Anim.Count > 0)
89 | {
90 | animationOverride = names.Anim
91 | .Where(x => (x.StudioId == data.StudioId) &&
92 | (x.Controller == data.ControllerFemale))
93 | .FirstOrDefault();
94 | if (animationOverride != null)
95 | {
96 | overrideName = true;
97 | }
98 | }
99 | }
100 |
101 | if (overrideName)
102 | {
103 | var name = data.AnimationName;
104 | #if KKS
105 | data.AnimationName = animationOverride?.KoikatsuSunshine;
106 | #endif
107 | #if KK
108 | data.AnimationName = animationOverride?.Koikatu;
109 | #endif
110 | #if DEBUG
111 | Log.Debug($"ProcessArray: Replacing name={name} with " +
112 | $"replace={data.AnimationName}.");
113 | #endif
114 | }
115 | else if (animationNamesDict.TryGetValue(guid, out var name))
116 | {
117 | if (name != null)
118 | {
119 | name.Anim.Add(animation);
120 | //animationNamesDict[guid].Anim.Add(animation);
121 | if (!_saveNames)
122 | {
123 | _saveNames = true;
124 | }
125 | }
126 | }
127 |
128 | }
129 | #if KKS
130 | // Assuming configuration is for KK like originally is and the
131 | // overrides are for KKS only no the other way around.
132 | // TODO: Changing it so it can be the other way around also.
133 | var overrideRoot = animElem?
134 | .Element(ManifestOverride)?
135 | .Element(KoikatuAPI.GameProcessName);
136 | if (overrideRoot != null)
137 | {
138 | var overrideReader = overrideRoot.CreateReader();
139 | var overrideData = (OverrideInfo)xmlOverrideSerializer
140 | .Deserialize(overrideReader);
141 | overrideReader.Close();
142 | DoOverrides(ref data, overrideData, ref animation, overrideName);
143 | }
144 | #endif
145 | if (!animationDict.TryGetValue(data.Mode, out var list))
146 | {
147 | animationDict[data.Mode] = list = [];
148 | }
149 | list.Add(data);
150 | if (specificFor)
151 | {
152 | specificLog.Append($"{GetAnimationKey(data),-37} - " +
153 | $"{Utilities.Translate(data.AnimationName)}\n");
154 | specificFor = false;
155 | }
156 | else
157 | {
158 | logLines.Append($"{GetAnimationKey(data),-37} - " +
159 | $"{Utilities.Translate(data.AnimationName)}\n");
160 | }
161 | count++;
162 | }
163 | if (specificLog.Length > 0)
164 | {
165 | logLines.AppendLine($"\nAnimations specific for " +
166 | $"{KoikatuAPI.GameProcessName}:\n\n{specificLog}");
167 | }
168 | logLines.Append('\n');
169 | return count;
170 | }
171 |
172 | internal static Func
173 | RootText = x => x == KoikatuAPI.GameProcessName ?
174 | $"Game specific elements of {x}" : $"Root elements of {x}";
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015/2017 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # Visual Studio 2017 auto generated files
34 | Generated\ Files/
35 |
36 | # MSTest test Results
37 | [Tt]est[Rr]esult*/
38 | [Bb]uild[Ll]og.*
39 |
40 | # NUNIT
41 | *.VisualState.xml
42 | TestResult.xml
43 |
44 | # Build Results of an ATL Project
45 | [Dd]ebugPS/
46 | [Rr]eleasePS/
47 | dlldata.c
48 |
49 | # Benchmark Results
50 | BenchmarkDotNet.Artifacts/
51 |
52 | # .NET Core
53 | project.lock.json
54 | project.fragment.lock.json
55 | artifacts/
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_h.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *_wpftmp.csproj
81 | *.log
82 | *.vspscc
83 | *.vssscc
84 | .builds
85 | *.pidb
86 | *.svclog
87 | *.scc
88 |
89 | # Chutzpah Test files
90 | _Chutzpah*
91 |
92 | # Visual C++ cache files
93 | ipch/
94 | *.aps
95 | *.ncb
96 | *.opendb
97 | *.opensdf
98 | *.sdf
99 | *.cachefile
100 | *.VC.db
101 | *.VC.VC.opendb
102 |
103 | # Visual Studio profiler
104 | *.psess
105 | *.vsp
106 | *.vspx
107 | *.sap
108 |
109 | # Visual Studio Trace Files
110 | *.e2e
111 |
112 | # TFS 2012 Local Workspace
113 | $tf/
114 |
115 | # Guidance Automation Toolkit
116 | *.gpState
117 |
118 | # ReSharper is a .NET coding add-in
119 | _ReSharper*/
120 | *.[Rr]e[Ss]harper
121 | *.DotSettings.user
122 |
123 | # JustCode is a .NET coding add-in
124 | .JustCode
125 |
126 | # TeamCity is a build add-in
127 | _TeamCity*
128 |
129 | # DotCover is a Code Coverage Tool
130 | *.dotCover
131 |
132 | # AxoCover is a Code Coverage Tool
133 | .axoCover/*
134 | !.axoCover/settings.json
135 |
136 | # Visual Studio code coverage results
137 | *.coverage
138 | *.coveragexml
139 |
140 | # NCrunch
141 | _NCrunch_*
142 | .*crunch*.local.xml
143 | nCrunchTemp_*
144 |
145 | # MightyMoose
146 | *.mm.*
147 | AutoTest.Net/
148 |
149 | # Web workbench (sass)
150 | .sass-cache/
151 |
152 | # Installshield output folder
153 | [Ee]xpress/
154 |
155 | # DocProject is a documentation generator add-in
156 | DocProject/buildhelp/
157 | DocProject/Help/*.HxT
158 | DocProject/Help/*.HxC
159 | DocProject/Help/*.hhc
160 | DocProject/Help/*.hhk
161 | DocProject/Help/*.hhp
162 | DocProject/Help/Html2
163 | DocProject/Help/html
164 |
165 | # Click-Once directory
166 | publish/
167 |
168 | # Publish Web Output
169 | *.[Pp]ublish.xml
170 | *.azurePubxml
171 | # Note: Comment the next line if you want to checkin your web deploy settings,
172 | # but database connection strings (with potential passwords) will be unencrypted
173 | *.pubxml
174 | *.publishproj
175 |
176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
177 | # checkin your Azure Web App publish settings, but sensitive information contained
178 | # in these scripts will be unencrypted
179 | PublishScripts/
180 |
181 | # NuGet Packages
182 | *.nupkg
183 | # The packages folder can be ignored because of Package Restore
184 | **/[Pp]ackages/*
185 | # except build/, which is used as an MSBuild target.
186 | !**/[Pp]ackages/build/
187 | # Uncomment if necessary however generally it will be regenerated when needed
188 | #!**/[Pp]ackages/repositories.config
189 | # NuGet v3's project.json files produces more ignorable files
190 | *.nuget.props
191 | *.nuget.targets
192 |
193 | # Microsoft Azure Build Output
194 | csx/
195 | *.build.csdef
196 |
197 | # Microsoft Azure Emulator
198 | ecf/
199 | rcf/
200 |
201 | # Windows Store app package directories and files
202 | AppPackages/
203 | BundleArtifacts/
204 | Package.StoreAssociation.xml
205 | _pkginfo.txt
206 | *.appx
207 |
208 | # Visual Studio cache files
209 | # files ending in .cache can be ignored
210 | *.[Cc]ache
211 | # but keep track of directories ending in .cache
212 | !*.[Cc]ache/
213 |
214 | # Others
215 | ClientBin/
216 | ~$*
217 | *~
218 | *.dbmdl
219 | *.dbproj.schemaview
220 | *.jfm
221 | *.pfx
222 | *.publishsettings
223 | orleans.codegen.cs
224 |
225 | # Including strong name files can present a security risk
226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
227 | #*.snk
228 |
229 | # Since there are multiple workflows, uncomment next line to ignore bower_components
230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
231 | #bower_components/
232 |
233 | # RIA/Silverlight projects
234 | Generated_Code/
235 |
236 | # Backup & report files from converting an old project file
237 | # to a newer Visual Studio version. Backup files are not needed,
238 | # because we have git ;-)
239 | _UpgradeReport_Files/
240 | Backup*/
241 | UpgradeLog*.XML
242 | UpgradeLog*.htm
243 | ServiceFabricBackup/
244 | *.rptproj.bak
245 |
246 | # SQL Server files
247 | *.mdf
248 | *.ldf
249 | *.ndf
250 |
251 | # Business Intelligence projects
252 | *.rdl.data
253 | *.bim.layout
254 | *.bim_*.settings
255 | *.rptproj.rsuser
256 |
257 | # Microsoft Fakes
258 | FakesAssemblies/
259 |
260 | # GhostDoc plugin setting file
261 | *.GhostDoc.xml
262 |
263 | # Node.js Tools for Visual Studio
264 | .ntvs_analysis.dat
265 | node_modules/
266 |
267 | # Visual Studio 6 build log
268 | *.plg
269 |
270 | # Visual Studio 6 workspace options file
271 | *.opt
272 |
273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
274 | *.vbw
275 |
276 | # Visual Studio LightSwitch build output
277 | **/*.HTMLClient/GeneratedArtifacts
278 | **/*.DesktopClient/GeneratedArtifacts
279 | **/*.DesktopClient/ModelManifest.xml
280 | **/*.Server/GeneratedArtifacts
281 | **/*.Server/ModelManifest.xml
282 | _Pvt_Extensions
283 |
284 | # Paket dependency manager
285 | .paket/paket.exe
286 | paket-files/
287 |
288 | # FAKE - F# Make
289 | .fake/
290 |
291 | # JetBrains Rider
292 | .idea/
293 | *.sln.iml
294 |
295 | # CodeRush personal settings
296 | .cr/personal
297 |
298 | # Python Tools for Visual Studio (PTVS)
299 | __pycache__/
300 | *.pyc
301 |
302 | # Cake - Uncomment if you are using it
303 | # tools/**
304 | # !tools/packages.config
305 |
306 | # Tabs Studio
307 | *.tss
308 |
309 | # Telerik's JustMock configuration file
310 | *.jmconfig
311 |
312 | # BizTalk build output
313 | *.btp.cs
314 | *.btm.cs
315 | *.odx.cs
316 | *.xsd.cs
317 |
318 | # OpenCover UI analysis results
319 | OpenCover/
320 |
321 | # Azure Stream Analytics local run output
322 | ASALocalRun/
323 |
324 | # MSBuild Binary and Structured Log
325 | *.binlog
326 |
327 | # NVidia Nsight GPU debugger configuration file
328 | *.nvuser
329 |
330 | # MFractors (Xamarin productivity tool) working folder
331 | .mfractor/
332 |
333 | # Local History for Visual Studio
334 | .localhistory/
335 |
336 | # Local configuraton
337 | /PostBuild.bat
338 | /.editorconfig
339 | /release.ps1
340 | /test.ps1
341 | Hooks.Test.cs
342 | AnimationLoader.KoikatsuSunshine.Test/
343 | rel/
344 |
--------------------------------------------------------------------------------
/Manifest.md:
--------------------------------------------------------------------------------
1 | # Manifest.xml for animations
2 |
3 | The current manifest.xml will continue to work. Both Koikatu (**KK**) and Koikatsu Sunshine
4 | (**KKS**) will try to load the two animations. The extensions described here are optional and made
5 | to have more control on how AnimationLoader will load the animations.
6 |
7 | ```xml
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | ```
64 |
65 | ## Changes
66 |
67 | 1- The NeckDonorId was been applied was been only applied to the female character. From version 1.1.2 it will be applied to both male and female and there are definitions for the character individually. This permits to assign different values to each character
68 |
69 | - NeckDonorIdFemale
70 | - NeckDonorIdFemale1
71 | - NeckDonorIdMale
72 | ## Extensions:
73 |
74 | 1- **PositionHeroine** and **PositionPlayer** - vectors that represent:
75 | - x axis if left and right movement (red axis)
76 | - y axis up and down (green axis)
77 | - z axis forward and backwards (blue axis)
78 |
79 | The values represent a factor or fraction of one unit of movement. For example:
80 | - to move one unit use 1
81 | - to move one and half units 1.5.
82 | - to move one fifth of a unit use 0.2
83 |
84 | The scale may be around a meter.
85 |
86 | 2- **GameSpecificOverrides** - Since the manifest.xml is the same for KK and KKS taking as a base that
87 | the definitions are for KK to make any adjustment for KKS a node can be added for KKS
88 | with value overrides for the animations that KKS will read.
89 |
90 | 3- **Nodes and ** - any animation inside these will be read by the
91 | corresponding game. I there are more than one animation that is exclusive or a game all can be inside
92 | the same node. The can also be mark individually if desired.
93 |
94 | ## Examples
95 |
96 | ### Example 1
97 |
98 | ```xml
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | 0
111 | anim_imports/kplug/female/02_40.unity3d
112 | anim_imports/kplug/male/02_40.unity3d
113 | khh_f_60
114 | khh_m_60
115 | Animation 0
116 | houshi
117 | Hand
118 |
119 | LieDown
120 | Stand
121 |
122 | 0
123 | 0
124 | false
125 | TitsPussy
126 |
127 |
128 |
129 | 1
130 | anim_imports/kplug/female/02_41.unity3d
131 | anim_imports/kplug/male/02_41.unity3d
132 | khh_f_61
133 | khh_m_61
134 | Animation 1
135 | houshi
136 | Hand
137 |
138 | LieDown
139 | Stand
140 |
141 | 0
142 | 55
143 | false
144 | TitsPussy
145 |
146 |
147 |
148 |
149 |
150 | ```
151 |
152 | This is a example of the current format. There is a problem beecause KK does not have NeckDonorId 55. To
153 | account for this there are two options. From here on I will just use the minimum number of fields in
154 | the examples.
155 |
156 | 1- The animation **Animation 1** only works for KKS. In this case inside the node AnimationLoader you
157 | can add a section `````` and move **Animation 1** there.
158 |
159 | ```xml
160 |
161 |
162 |
163 | 0
164 | Animation 0
165 |
166 |
167 |
168 |
169 | 1
170 | Animation 1
171 | 55
172 |
173 |
174 |
175 |
176 | ```
177 | This way only KKS will try to load **Animation 1**. KK will ignore the **KoikatsuSunshine** node.
178 | Exclusive animations can be all in one section or marked individually.
179 |
180 | 2- **Animation 1** works for both games but for KKS works better with NeckDonorId 55.
181 | ```xml
182 |
183 |
184 |
185 | 0
186 | Animation 0
187 |
188 |
189 |
190 | 1
191 | Animation 1
192 | 0
193 |
194 |
195 | 55
196 |
197 |
198 |
199 |
200 |
201 | ```
202 | GameSpecificOverrides only work for KKS do to Unity manage code stripping having an element with xml
203 | will trigger errors. But since the current definitions are for KK anyway this should not be a
204 | limitation. Animations targeting KK and KKS should always start with the definition for KK then do
205 | any fine tune for KKS with the overrides.
206 |
207 | ### Example 2
208 |
209 | The position of the characters can be adjusted.
210 |
211 | ```xml
212 |
213 | 0
214 | 0
215 | 1
216 |
217 |
218 | 0
219 | 0
220 | 1
221 |
222 | ```
223 |
224 | Move the character forward 1 unit.
225 |
226 | The characters are move individually only one position adjustment can be made no need to have a move
227 | configuration for both characters.
228 |
229 | ```xml
230 |
231 | 0
232 | 0
233 | -0.04
234 |
235 | ```
236 |
237 | Here it shows move the Heroine 0.04 fraction of a unit backwards.
238 |
239 | ### Example 3
240 |
241 | ```xml
242 | 12
243 | 2
244 | ```
245 | Applay NeckDonor 12 for the female character and 2 for the male.
246 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Fixes/Move.AnimationInfo.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Class to help identify the animations
3 | //
4 | using System.Collections.Generic;
5 |
6 | using UnityEngine;
7 |
8 | using KKAPI;
9 |
10 | using static HFlag;
11 |
12 |
13 | namespace AnimationLoader
14 | {
15 | public partial class SwapAnim
16 | {
17 | ///
18 | /// Class to help uniquely identified the animations
19 | ///
20 | public class AnimationInfo
21 | {
22 | internal SwapAnimationInfo _anim;
23 | internal HSceneProc.AnimationListInfo _animGA;
24 | internal bool _isAnimationLoader;
25 |
26 | internal string _Guid;
27 | internal EMode _mode;
28 | internal int _id;
29 | internal string _controller;
30 | internal string _name;
31 | internal int _donor;
32 |
33 | internal string Controller => _controller;
34 | public int Donor => _donor;
35 | public string Guid => _Guid;
36 | public int Id => _id;
37 | public string Key => $"{_Guid}-{_mode}-{_controller}-{_id:D3}";
38 | public EMode Mode => _mode;
39 | public string Name => _name;
40 | public bool IsAnimationLoader => _isAnimationLoader;
41 | public SwapAnimationInfo SwapAnim => _anim;
42 | public HSceneProc.AnimationListInfo Animation => _animGA;
43 |
44 | ///
45 | /// These are the fields for the key
46 | /// _Guid - The guid of the zipmod
47 | /// _mode - Animator mode (aibu, hoshi, ..)
48 | /// _controller - Controller for the animation some zipmod don't define StudioId
49 | /// this is added to make key unique
50 | /// _id = Studio ID
51 | ///
52 | public AnimationInfo()
53 | {
54 | _Guid = string.Empty;
55 | _mode = EMode.none;
56 | _controller = string.Empty;
57 | _id = -1;
58 | _name = string.Empty;
59 | _donor = -1;
60 | }
61 |
62 | public AnimationInfo(HSceneProc.AnimationListInfo animation)
63 | {
64 | AnimationInfoHelper(animation);
65 | }
66 |
67 | internal void AnimationInfoHelper(HSceneProc.AnimationListInfo animation)
68 | {
69 | _mode = animation.mode;
70 | swapAnimationMapping.TryGetValue(animation, out var anim);
71 | if (anim != null)
72 | {
73 | // AnimationLoader animation
74 | _anim = anim;
75 | _animGA = null;
76 | _controller = anim.ControllerFemale;
77 | _Guid = anim.Guid;
78 | _id = anim.StudioId;
79 | _isAnimationLoader = true;
80 | _name = anim.AnimationName;
81 | _donor = anim.DonorPoseId;
82 | }
83 | else
84 | {
85 | // Game animation
86 | _anim = null;
87 | _animGA = animation;
88 | _controller = animation.paramFemale.path.file;
89 | _Guid = KoikatuAPI.GameProcessName;
90 | _id = animation.id;
91 | _isAnimationLoader = false;
92 | _name = animation.nameAnimation;
93 | _donor = -1;
94 | }
95 | }
96 |
97 | public void SetAnimation(object animation)
98 | {
99 | AnimationInfoHelper((HSceneProc.AnimationListInfo)animation);
100 | }
101 |
102 | public string TranslateName()
103 | {
104 | return Utilities.TranslateName(_name);
105 | }
106 |
107 | public object Anim()
108 | {
109 | if (_anim == null)
110 | {
111 | return _animGA;
112 | }
113 | return _anim;
114 | }
115 |
116 | }
117 |
118 | ///
119 | /// Static function to get the key for any animation
120 | ///
121 | ///
122 | ///
123 | ///
124 | public static string GetAnimationKey(HSceneProc.AnimationListInfo animation)
125 | {
126 | string Guid;
127 | EMode mode;
128 | int id;
129 | string controller;
130 |
131 | Guid = "com.illusion";
132 | mode = animation.mode;
133 | id = animation.id;
134 | controller = animation.paramFemale.path.file;
135 |
136 | if (swapAnimationMapping != null)
137 | {
138 | swapAnimationMapping.TryGetValue(animation, out var anim);
139 | if (anim != null)
140 | {
141 | Guid = anim.Guid;
142 | id = anim.StudioId;
143 | controller = anim.ControllerFemale;
144 | }
145 | }
146 |
147 | return $"{Guid}-{mode}-{controller}-{id:D3}";
148 | }
149 |
150 | ///
151 | /// GetKey overload
152 | ///
153 | ///
154 | ///
155 | public static string GetAnimationKey(SwapAnimationInfo animation)
156 | {
157 | return $"{animation.Guid}-{animation.Mode}-{animation.ControllerFemale}" +
158 | $"-{animation.StudioId:D3}";
159 | }
160 |
161 | public static List Controllers(HSceneProc.AnimationListInfo animation)
162 | {
163 | var result = new List();
164 |
165 | result.Clear();
166 |
167 |
168 | if (swapAnimationMapping != null)
169 | {
170 | swapAnimationMapping.TryGetValue(animation, out var anim);
171 | if (anim != null)
172 | {
173 | return Controllers(anim);
174 | }
175 | }
176 |
177 | if (animation.paramFemale.path.file != null)
178 | {
179 | result.Add(animation.paramFemale.path.file);
180 | }
181 | if (animation.paramMale.path.file != null)
182 | {
183 | result.Add(animation.paramMale.path.file);
184 | }
185 | if (animation.paramFemale1.path.file != null)
186 | {
187 | result.Add(animation.paramFemale1.path.file);
188 | }
189 |
190 | return result;
191 | }
192 |
193 |
194 | public static List Controllers(SwapAnimationInfo animation)
195 | {
196 | var result = new List();
197 |
198 | result.Clear();
199 |
200 | if (animation.ControllerFemale != null)
201 | {
202 | result.Add(animation.ControllerFemale);
203 | }
204 | if (animation.ControllerMale != null)
205 | {
206 | result.Add(animation.ControllerMale);
207 | }
208 | if (animation.ControllerFemale1 != null)
209 | {
210 | result.Add(animation.ControllerFemale1);
211 | }
212 |
213 | return result;
214 | }
215 |
216 | ///
217 | /// Get a list with the positions adjustment in the manifest. If it is a regular
218 | /// animation it will return zero vectors
219 | ///
220 | /// animation to search
221 | ///
222 | public static List GetAnimationMovement(HSceneProc.AnimationListInfo animation)
223 | {
224 | List result = [new Vector3(0, 0, 0), new Vector3(0, 0, 0)];
225 |
226 | if (swapAnimationMapping != null)
227 | {
228 | swapAnimationMapping.TryGetValue(animation, out var anim);
229 | if (anim != null)
230 | {
231 | result[0] = anim.PositionPlayer;
232 | result[1] = anim.PositionHeroine;
233 | }
234 | }
235 | return result;
236 | }
237 |
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Koikatu/Hooks.LoadMotionList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using TMPro;
6 | using UnityEngine;
7 | using UnityEngine.EventSystems;
8 | using UnityEngine.UI;
9 |
10 | using IllusionUtility.GetUtility;
11 |
12 | using HarmonyLib;
13 | using static ADV.Info;
14 |
15 |
16 | namespace AnimationLoader
17 | {
18 | public partial class SwapAnim
19 | {
20 | internal partial class Hooks
21 | {
22 | [HarmonyPostfix]
23 | [HarmonyPatch(typeof(HSprite), nameof(HSprite.LoadMotionList))]
24 | private static void LoadMotionList(
25 | HSprite __instance,
26 | List _lstAnimInfo,
27 | GameObject _objParent)
28 | {
29 | if (_lstAnimInfo == null || _lstAnimInfo.Count == 0)
30 | {
31 | return;
32 | }
33 | var buttonParent = _objParent.transform;
34 | var buttons = _objParent.transform.Cast().ToList();
35 | Transform scrollT = null;
36 | if (VRHSceneType != null || UseGrid.Value)
37 | {
38 | DestroyImmediate(_objParent.GetComponent());
39 | DestroyImmediate(_objParent.GetComponent());
40 | DestroyImmediate(_objParent.GetComponent());
41 | var glg = _objParent.AddComponent();
42 | glg.cellSize = new Vector2(200, 35);
43 | glg.startAxis = GridLayoutGroup.Axis.Vertical;
44 | glg.startCorner = GridLayoutGroup.Corner.UpperRight;
45 | glg.constraint = GridLayoutGroup.Constraint.FixedRowCount;
46 | glg.constraintCount = 15;
47 | glg.childAlignment = TextAnchor.UpperRight;
48 | }
49 | else
50 | {
51 | var go = DefaultControls.CreateScrollView(new DefaultControls.Resources());
52 | go.transform.SetParent(_objParent.transform, false);
53 | var scroll = go.GetComponent();
54 | scroll.horizontal = false;
55 | scroll.scrollSensitivity = 32f;
56 | scroll.movementType = ScrollRect.MovementType.Clamped;
57 | scroll.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide;
58 | scroll.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide;
59 | DestroyImmediate(scroll.horizontalScrollbar.gameObject);
60 | DestroyImmediate(scroll.verticalScrollbar.gameObject);
61 | DestroyImmediate(scroll.GetComponent());
62 |
63 | var copyTarget = GameObject
64 | .Find("Canvas").transform
65 | .Find("clothesFileWindow/Window/WinRect/ListArea/Scroll " +
66 | "View/Scrollbar Vertical").gameObject;
67 | var newScrollbar = Instantiate(copyTarget, go.transform);
68 | scroll.verticalScrollbar = newScrollbar.GetComponent();
69 | newScrollbar.transform.SetRect(1f, 0f, 1f, 1f, 0f, 0f, 18f);
70 |
71 | var triggerEvent = new EventTrigger.TriggerEvent();
72 | triggerEvent.AddListener(x => GlobalMethod.SetCameraMoveFlag(
73 | __instance.flags.ctrlCamera,
74 | false));
75 | var eventTrigger = newScrollbar.AddComponent();
76 | eventTrigger.triggers.Add(new EventTrigger.Entry {
77 | eventID = EventTriggerType.PointerDown, callback = triggerEvent });
78 |
79 | var vlg = _objParent.GetComponent();
80 | var csf = _objParent.GetComponent();
81 | vlg.enabled = false;
82 | csf.enabled = false;
83 | CopyComponent(vlg, scroll.content.gameObject).enabled = true;
84 | CopyComponent(csf, scroll.content.gameObject).enabled = true;
85 |
86 | buttonParent = scroll.content;
87 | scrollT = scroll.gameObject.transform;
88 | }
89 |
90 | // remove the buttons as we're going to rebuild the entire list
91 | buttons.ForEach(x => Destroy(x.gameObject));
92 |
93 | foreach (var anim in _lstAnimInfo)
94 | {
95 | var btn = Instantiate(__instance.objMotionListNode, buttonParent, false);
96 | btn.AddComponent().info = anim;
97 | var label = btn.GetComponentInChildren();
98 | label.text = anim.nameAnimation;
99 | label.color = Color.black;
100 |
101 | //TODO: what
102 | var tgl = btn.GetComponent();
103 | tgl.group = _objParent.GetComponent();
104 | tgl.enabled = false;
105 | tgl.enabled = true;
106 |
107 | btn.GetComponent().listClickAction.Add(() =>
108 | {
109 | __instance.OnChangePlaySelect(btn);
110 | });
111 |
112 | btn.SetActive(true);
113 | if (__instance.flags.nowAnimationInfo == anim)
114 | {
115 | btn.GetComponent().isOn = true;
116 | }
117 |
118 | swapAnimationMapping.TryGetValue(anim, out var swap);
119 | if (swap != null)
120 | {
121 | btn.transform
122 | .FindLoop("Background").GetComponent().color = buttonColor;
123 | label.text = swap.AnimationName;
124 | }
125 | }
126 |
127 | // order all buttons by name
128 | var allButtons = buttonParent.Cast()
129 | .OrderBy(x => x.GetComponentInChildren().text).ToList();
130 | foreach (var t in allButtons)
131 | {
132 | // disable New text
133 | var newT = t.FindLoop("New");
134 | if (newT)
135 | {
136 | //newT.gameObject.SetActive(false);
137 | newT.SetActive(false);
138 | }
139 |
140 | if (SortPositions.Value)
141 | {
142 | t.SetAsLastSibling();
143 | }
144 |
145 | //var textMeshGo = t.FindLoop("TextMeshPro Text").gameObject;
146 | var textMeshGo = t.FindLoop("TextMeshPro Text");
147 | var textMesh = textMeshGo.GetComponent();
148 | textMesh.enableWordWrapping = false;
149 | textMesh.overflowMode = TextOverflowModes.Overflow; // disable ... after text
150 |
151 | // add scrolling text if text is long enough
152 | var rectT = (RectTransform)t;
153 | if (rectT.sizeDelta.x < textMesh.preferredWidth)
154 | {
155 | textMesh.alignment = TextAlignmentOptions.CaplineLeft;
156 |
157 | var txtScroll = textMeshGo.AddComponent();
158 | txtScroll.textMesh = textMesh;
159 | txtScroll.transBase = rectT;
160 | }
161 | }
162 |
163 | if (scrollT != null && allButtons.Count > 8)
164 | {
165 | scrollT.SetRect(0f, 0f, 1f, 1f, -5f, -100f, -5f, 100f);
166 | }
167 | }
168 | }
169 |
170 | private static T CopyComponent(T original, GameObject destination) where T : Component
171 | {
172 | var type = original.GetType();
173 | var dst = destination.GetComponent(type) as T;
174 | if (!dst)
175 | {
176 | dst = destination.AddComponent(type) as T;
177 | }
178 |
179 | var fields = type.GetFields();
180 | foreach (var field in fields)
181 | {
182 | if (field.IsStatic)
183 | {
184 | continue;
185 | }
186 |
187 | field.SetValue(dst, field.GetValue(original));
188 | }
189 | var props = type.GetProperties();
190 | foreach (var prop in props)
191 | {
192 | if (!prop.CanWrite || !prop.CanWrite || prop.Name == "name")
193 | {
194 | continue;
195 | }
196 |
197 | prop.SetValue(dst, prop.GetValue(original, null), null);
198 | }
199 | return dst;
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/AnimationsNames.cs:
--------------------------------------------------------------------------------
1 | //
2 | // For people who keep their own names for the animations or translate them to other
3 | // language.
4 | //
5 | // Save animations names to UserData/AnimationLoader/Names so they are not overwritten by
6 | // updates. They can be maintained and updated there.
7 | //
8 | // Only animations not in the file are added. No animation in the file will be
9 | // overwritten
10 | //
11 | using System;
12 | using System.Collections.Generic;
13 | using System.IO;
14 | using System.Linq;
15 | using System.Xml.Linq;
16 | using System.Xml.Serialization;
17 |
18 | using Sideloader;
19 |
20 |
21 | namespace AnimationLoader
22 | {
23 | public partial class SwapAnim
24 | {
25 | private static readonly XmlSerializer xmlNamesSerializer = new(typeof(Names));
26 | private static Dictionary animationNamesDict = [];
27 |
28 | #region Serializable classes
29 | [XmlRoot("Names")]
30 | [Serializable]
31 | public class Names
32 | {
33 | [XmlElement]
34 | public string guid = string.Empty;
35 |
36 | [XmlElement]
37 | public string name = string.Empty;
38 |
39 | [XmlElement]
40 | public string version = string.Empty;
41 |
42 | [XmlElement]
43 | public string author = string.Empty;
44 |
45 | [XmlElement]
46 | public string description = string.Empty;
47 |
48 | [XmlElement]
49 | public string website = string.Empty;
50 |
51 | [XmlArray("AnimationLoader")]
52 | public List Anim = [];
53 | }
54 |
55 | //
56 | // A real dictionary was better for programming but less friendly to user interaction
57 | //
58 | [XmlRoot("Animation")]
59 | [Serializable]
60 | public class Animation
61 | {
62 | [XmlElement]
63 | public int StudioId;
64 | [XmlElement]
65 | public string Controller;
66 | [XmlElement]
67 | public string Koikatu;
68 | [XmlElement]
69 | public string KoikatuReference;
70 | [XmlElement]
71 | public string KoikatsuSunshine;
72 | [XmlElement]
73 | public string KoikatsuSunshineReference;
74 | }
75 | #endregion
76 |
77 | ///
78 | /// Read animations names xml files
79 | ///
80 | private static void LoadNamesXml()
81 | {
82 | if (!UserOverrides.Value)
83 | {
84 | Log.Debug($"[LoadNamesXml]: Animations names disabled by user.");
85 | return;
86 | }
87 |
88 | var path = Path.Combine(UserData.Path, "AnimationLoader/Names");
89 | if (Directory.Exists(path))
90 | {
91 | var docs = Directory.GetFiles(path, "*.xml").Select(XDocument.Load).ToList();
92 | if (docs.Count > 0)
93 | {
94 | Log.Debug($"0001: [{PluginName}] Loading animations names.");
95 | LoadNamesXmls(docs);
96 | return;
97 | }
98 | }
99 | // Create empty dictionary
100 | InitAnimationNamesDict();
101 | Log.Debug("0002: No names found.");
102 | }
103 |
104 | ///
105 | /// Create empty dictionary for any animation bundle found
106 | ///
107 | private static void InitAnimationNamesDict()
108 | {
109 | var manifests = Sideloader.Sideloader.Manifests.Values.Select(x => x.ManifestDocument);
110 |
111 | foreach (var manifest in manifests
112 | .Select(x => x.Root)
113 | .Where(x => x?.Element(ManifestRootElement) != null))
114 | {
115 | var guid = manifest.Element("guid")?.Value;
116 | Log.Debug($"InitAnimationNamesDict: Add GUID={guid}");
117 | NamesAddGuidHelper(manifest);
118 | }
119 | }
120 |
121 | ///
122 | /// Add the names on the xml files to names dictionary
123 | ///
124 | ///
125 | private static void LoadNamesXmls(IEnumerable namesDocs)
126 | {
127 | if (!UserOverrides.Value)
128 | {
129 | return;
130 | }
131 |
132 | foreach (var animElem in namesDocs.Select(x => x.Root))
133 | {
134 | try
135 | {
136 | var reader = animElem.CreateReader();
137 | var names = (Names)xmlNamesSerializer.Deserialize(reader);
138 | reader.Close();
139 | animationNamesDict.Add(names.guid, names);
140 | #if DEBUG
141 | if (names?.Anim.Count > 0)
142 | {
143 | foreach (var a in names.Anim)
144 | {
145 | Log.Info($"Animation {a.StudioId} ref={a.KoikatuReference} " +
146 | $"trans={a.KoikatsuSunshine}");
147 | }
148 | }
149 | #endif
150 | }
151 | catch (Exception ex)
152 | {
153 | Log.Error($"0003: Error trying to read names file. " +
154 | $"GUID={animElem.Name} " +
155 | $"{ex.Message}");
156 | }
157 | }
158 | }
159 |
160 | ///
161 | /// Setup new names for guid
162 | ///
163 | ///
164 | private static void NamesAddGuid(XElement manifest)
165 | {
166 | if (!UserOverrides.Value)
167 | {
168 | return;
169 | }
170 |
171 | NamesAddGuidHelper(manifest);
172 | }
173 |
174 | private static void NamesAddGuidHelper(XElement manifest)
175 | {
176 | // new names
177 | var names = new Names();
178 |
179 | // initialize fields with manifest date
180 | names.guid = manifest.Element(nameof(names.guid)).Value;
181 | names.name = manifest.Element(nameof(names.name)).Value;
182 | names.version = manifest.Element(nameof(names.version)).Value;
183 | names.author = manifest.Element(nameof(names.author)).Value;
184 | names.description = "Modify the items Koikatu and KoikatsuSunshine only.";
185 | names.website = manifest.Element(nameof(names.website)).Value;
186 |
187 | // add new names to dictionary
188 | animationNamesDict.Add(names.guid, names);
189 | }
190 |
191 | ///
192 | /// Save all the names in the names dictionary to xml files
193 | ///
194 | private static void SaveNamesXmls()
195 | {
196 | if (!UserOverrides.Value)
197 | {
198 | return;
199 | }
200 |
201 | foreach (var guid in animationNamesDict.Keys)
202 | {
203 | var animNames = animationNamesDict[guid];
204 | SaveNames(animNames, guid);
205 | }
206 | }
207 |
208 | ///
209 | /// Save names to xml files. One xml file is saved per guid in dictionary.
210 | ///
211 | ///
212 | ///
213 | private static void SaveNames(Names names, string guid, bool overwrite = false)
214 | {
215 | if (!UserOverrides.Value)
216 | {
217 | return;
218 | }
219 |
220 | var path = Path.Combine(UserData.Path, "AnimationLoader/Names");
221 | var fileName = $"{path}/names-{guid}.xml";
222 | FileInfo file = new(fileName);
223 |
224 | Log.Debug($"0004: Saving names file {fileName}.");
225 |
226 | // Create directory where to save the file.
227 | // Safe to do even if directory exists.
228 | file.Directory.Create();
229 | if (file.Exists && !overwrite)
230 | {
231 | Log.Debug($"0005: File {fileName} already exits not overwritten.");
232 | return;
233 | }
234 |
235 | XmlSerializer xmlSerializer = new(typeof(Names));
236 | var writer = new StreamWriter($"{path}/names-{guid}.xml");
237 | xmlSerializer.Serialize(writer.BaseStream, names);
238 | writer.Close();
239 | }
240 |
241 | ///
242 | /// Read the names from an xml file
243 | ///
244 | ///
245 | ///
246 | private static void ReadNames(ref Names names, string guid)
247 | {
248 | if (!UserOverrides.Value)
249 | {
250 | return;
251 | }
252 |
253 | var path = Path.Combine(UserData.Path, "AnimationLoader/Names");
254 | var fileName = $"{path}/names-{guid}.xml";
255 | FileInfo file = new(fileName);
256 |
257 | if (!file.Exists)
258 | {
259 | Log.Debug($"0006: Names file {fileName} not found. Safe to ignore.");
260 | return;
261 | }
262 |
263 | XmlSerializer xmlSerializer = new(typeof(Names));
264 | StreamReader reader = new($"{path}/names-{guid}.xml");
265 | names = (Names)xmlSerializer.Deserialize(reader);
266 | reader.Close();
267 | }
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Plugin.ConfigEntries.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Configuration entries
3 | //
4 | using ADV.Commands.Base;
5 |
6 | using BepInEx.Configuration;
7 | using BepInEx.Logging;
8 |
9 | using KKAPI;
10 | using KKAPI.Utilities;
11 |
12 | using UnityEngine;
13 |
14 |
15 | namespace AnimationLoader
16 | {
17 | public partial class SwapAnim
18 | {
19 | #if KKS
20 | internal static ConfigEntry LoadInCharStudio { get; set; }
21 | internal static ConfigEntry UseAnimationLevels { get; set; }
22 | internal static ConfigEntry EnableAllFreeH { get; set; }
23 | #endif
24 | #if KK
25 | private static ConfigEntry UseGrid { get; set; }
26 | #endif
27 | #if DEBUG
28 | internal static ConfigEntry TestMode { get; set; }
29 | #endif
30 | internal static ConfigEntry DebugInfo { get; set; }
31 | internal static ConfigEntry DebugToConsole { get; set; }
32 | internal static ConfigEntry UserOverrides { get; set; }
33 | internal static ConfigEntry ReloadManifests { get; set; }
34 |
35 | internal static ConfigEntry Reposition { get; set; }
36 | internal static ConfigEntry SortPositions { get; set; }
37 | internal static ConfigEntry HighLight { get; set; }
38 | internal static ConfigEntry MotionIK { get; set; }
39 |
40 | internal const string GeneralSection = "General";
41 | internal const string DebugSection = "Debug";
42 | internal const string AdvanceSection = "Advance";
43 |
44 | internal void ConfigEntries()
45 | {
46 | #if KKS
47 | LoadInCharStudio = Config.Bind(
48 | section: GeneralSection,
49 | key: "Character Studio",
50 | defaultValue: true,
51 | configDescription: new ConfigDescription(
52 | description: "Enable/Disabled module for Studio",
53 | tags: new ConfigurationManagerAttributes { Order = 35 }));
54 |
55 | if (KoikatuAPI.GetCurrentGameMode() == GameMode.Studio)
56 | {
57 | if (!LoadInCharStudio.Value)
58 | {
59 | Log.Message("0013: MOD disabled in configuration.");
60 | enabled = false;
61 | return;
62 | }
63 | }
64 |
65 | UseAnimationLevels = Config.Bind(
66 | section: GeneralSection,
67 | key: "Use Experience Levels",
68 | defaultValue: true,
69 | configDescription: new ConfigDescription(
70 | description: "Apply experience levels to animations",
71 | tags: new ConfigurationManagerAttributes { Order = 16}));
72 |
73 | EnableAllFreeH = Config.Bind(
74 | section: GeneralSection,
75 | key: "All Animations in FreeH",
76 | defaultValue: false,
77 | configDescription: new ConfigDescription(
78 | description: "Enable all loaded animations in FreeH without needing to use them " +
79 | "in story mode",
80 | tags: new ConfigurationManagerAttributes { Order = 15}));
81 | #endif
82 | #if KK
83 | // TODO: Grid UI for KKS
84 | // How many animations will require a scrollable grid
85 | UseGrid = Config.Bind(
86 | section: GeneralSection,
87 | key: nameof(UseGrid),
88 | defaultValue: false,
89 | configDescription: new ConfigDescription(
90 | description: "If you don't want to use the scrollable" +
91 | " list for some reason",
92 | tags: new ConfigurationManagerAttributes { Order = 6 }));
93 | #endif
94 |
95 | // Load manifests from configuration directory
96 | ReloadManifests = Config.Bind(
97 | section: GeneralSection,
98 | key: nameof(ReloadManifests),
99 | defaultValue: new KeyboardShortcut(KeyCode.None),
100 | configDescription: new ConfigDescription(
101 | description: "Load positions from all manifest format xml files inside " +
102 | "config/AnimationLoader folder",
103 | tags: new ConfigurationManagerAttributes { Order = 39 }));
104 |
105 | SortPositions = Config.Bind(
106 | section: GeneralSection,
107 | key: nameof(SortPositions),
108 | defaultValue: true,
109 | configDescription: new ConfigDescription(
110 | description: "Sort positions alphabetically",
111 | tags: new ConfigurationManagerAttributes { Order = 37 }));
112 |
113 | // Reposition characters in the animations it can help with clipping
114 | Reposition = Config.Bind(
115 | section: AdvanceSection,
116 | key: "Reposition Characters",
117 | defaultValue: true,
118 | configDescription: new ConfigDescription(
119 | description: "Some animations have information in the manifest to move "
120 | + "the characters",
121 | acceptableValues: null,
122 | tags: new ConfigurationManagerAttributes { Order = 19, IsAdvanced = true }));
123 |
124 | // Highlight text in buttons helps identify store effects when used
125 | HighLight = Config.Bind(
126 | section: AdvanceSection,
127 | key: "Highlight Buttons Text",
128 | defaultValue: true,
129 | configDescription: new ConfigDescription(
130 | description: "Uses more color to highlight the effects of the "
131 | + "store plug-in and Free-H",
132 | acceptableValues: null,
133 | tags: new ConfigurationManagerAttributes { Order = 18, IsAdvanced = true }));
134 |
135 | // Reposition characters in the animations it can help with clipping
136 | MotionIK = Config.Bind(
137 | section: AdvanceSection,
138 | key: "Setup Motion IK",
139 | defaultValue: true,
140 | configDescription: new ConfigDescription(
141 | description: "Some animations have motion IK configurations if color" +
142 | " is used they can be identified by a darker yellow color",
143 | acceptableValues: null,
144 | tags: new ConfigurationManagerAttributes { Order = 17, IsAdvanced = true }));
145 |
146 |
147 | // Save names in UserData. These can be expanded to other fields if need be.
148 | UserOverrides = Config.Bind(
149 | section: GeneralSection,
150 | key: "Save Names",
151 | defaultValue: false,
152 | configDescription: new ConfigDescription(
153 | description: "Save and use names stored in UserData/AnimationLoader/Names. " +
154 | "Names then can be customized and won't be overwritten by updates.",
155 | tags: new ConfigurationManagerAttributes { Order = 33 }));
156 |
157 | // To generate debug information this has to be enabled
158 | // The majority of the Logs are in conditional compilation
159 | DebugInfo = Config.Bind(
160 | section: DebugSection,
161 | key: "Debug Information",
162 | defaultValue: false,
163 | configDescription: new ConfigDescription(
164 | description: "Show debug information",
165 | acceptableValues: null,
166 | tags: new ConfigurationManagerAttributes { Order = 29, IsAdvanced = true }));
167 | DebugInfo.SettingChanged += (_sender, _args) =>
168 | {
169 | Log.Enabled = DebugInfo.Value;
170 | #if DEBUG
171 | Log.Level(LogLevel.Info, $"0028: Log.Enabled set to {Log.Enabled}");
172 | #endif
173 | };
174 |
175 | DebugToConsole = Config.Bind(
176 | section: DebugSection,
177 | key: "Debug information to Console",
178 | defaultValue: false,
179 | configDescription: new ConfigDescription(
180 | description: "Show debug information in Console",
181 | acceptableValues: null,
182 | tags: new ConfigurationManagerAttributes {
183 | Order = 27,
184 | IsAdvanced = true
185 | }));
186 | DebugToConsole.SettingChanged += (_sender, _args) =>
187 | {
188 | Log.DebugToConsole = DebugToConsole.Value;
189 | #if DEBUG
190 | Log.Level(LogLevel.Info, $"[ConfigEntries] Log.DebugToConsole set to " +
191 | $"{Log.DebugToConsole}");
192 | #endif
193 | };
194 | #if DEBUG
195 | Log.Level(LogLevel.Info, $"0028: Log.Enabled set to {Log.Enabled}");
196 |
197 |
198 | TestMode = Config.Bind(
199 | section: DebugSection,
200 | key: "Enable Test Mode",
201 | defaultValue: false,
202 | configDescription: new ConfigDescription(
203 | description: "Allow unused animations in Free-H",
204 | acceptableValues: null,
205 | tags: new ConfigurationManagerAttributes { Order = 26, IsAdvanced = true }));
206 | #endif
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Hooks.Animators.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | using UnityEngine;
5 |
6 | using KKAPI.MainGame;
7 |
8 | #if DEBUG
9 | using BepInEx.Logging;
10 | #endif
11 | using HarmonyLib;
12 |
13 |
14 | namespace AnimationLoader
15 | {
16 | public partial class SwapAnim
17 | {
18 | internal enum State { On = 0, Shift = 1, Hang = 2, Off = 3 }
19 |
20 | internal partial class Hooks
21 | {
22 | ///
23 | /// Set the new original position when changing positions
24 | ///
25 | ///
26 | [HarmonyPrefix]
27 | [HarmonyPatch(typeof(HSceneProc), nameof(HSceneProc.ChangeAnimator))]
28 | private static void ChangeAnimatorPrefix(
29 | object __instance,
30 | HSceneProc.AnimationListInfo _nextAinmInfo)
31 | {
32 | // Temporarily disable movement in KK TODO: Get start position without
33 | // using the characters current position to get the value.
34 | if (_nextAinmInfo == null)
35 | {
36 | return;
37 | }
38 |
39 | if (_heroine == null)
40 | {
41 | return;
42 | }
43 |
44 | var heroine = GameAPI.GetCurrentHeroine();
45 | var animationKey = GetAnimationKey(_nextAinmInfo);
46 |
47 | // Get shoes off for foot job animations in
48 | // /BepInEx/config/AnimationLoader/FootJob/FootJobAnimations.xml
49 | // Check in KK somehow works not that intuitive
50 | if ((heroine != null) && _footJobAnimations.Contains(animationKey))
51 | {
52 | heroine.chaCtrl.SetClothesState(
53 | (int)ChaFileDefine.ClothesKind.shoes_inner,
54 | (byte)State.Off);
55 | Log.Debug("0019: [ChangeAnimatorPrefix] Taking shoes off.");
56 | }
57 | #if DEBUG
58 | if (heroine == null)
59 | {
60 | Log.Debug("0019: [ChangeAnimatorPrefix] Heroine is null.");
61 | }
62 | #endif
63 |
64 | #if KKS
65 | try
66 | {
67 | var hSceneTraverse = Traverse.Create(__instance);
68 | var flags = hSceneTraverse.Field("flags").Value;
69 | var position = hSceneTraverse
70 | .Field("nowHpointDataPos").Value;
71 | var lstFemales = hSceneTraverse
72 | .Field>("lstFemale").Value;
73 | var key = GetAnimationKey(_nextAinmInfo);
74 | if (_animationsUseStats.Stats.ContainsKey(key))
75 | {
76 | _animationsUseStats.Stats[key] += 1;
77 | }
78 | #if DEBUG
79 | var nowAnimName = "None";
80 | if (flags.nowAnimationInfo != null)
81 | {
82 | nowAnimName = Utilities
83 | .TranslateName(flags.nowAnimationInfo.nameAnimation);
84 | }
85 |
86 | Log.Warning($"0007: [ChangeAnimatorPrefix] Animator changing - " +
87 | $"[{Manager.Scene.ActiveScene.name}]\n" +
88 | $"Now Animation {nowAnimName} " +
89 | $"Animation {Utilities.TranslateName(_nextAinmInfo.nameAnimation)} " +
90 | $"Key={GetAnimationKey(_nextAinmInfo)} " +
91 | $"SiruPaste={SiruPaste(_nextAinmInfo.paramFemale.fileSiruPaste)}\n" +
92 | $"nowHpointDataPos={position.Format()}");
93 | #endif
94 | // Reposition characters before animation starts
95 | if (Reposition.Value)
96 | {
97 | var nowAnimationInfo = flags.nowAnimationInfo;
98 | var nowAnim = new AnimationInfo(flags.nowAnimationInfo);
99 | // var heroinePos =
100 | // GetMoveController(_heroine).ChaControl.transform.position;
101 | // Cannot use _heroine this call is prior to HSceneProc.SetShortcut
102 | // _heroine is still null
103 | var heroinePos = lstFemales[0].transform.position;
104 |
105 | if (nowAnim != null)
106 | {
107 | // If there is a position adjustment
108 | // Reset position for new animation in same HPoint
109 | if (Utilities.HasMovement(nowAnim))
110 | {
111 | if (nowAnim.SwapAnim.PositionHeroine != Vector3.zero)
112 | {
113 | if (!Utilities.IsNewPosition(_heroine))
114 | {
115 | GetMoveController(_heroine).ResetPosition();
116 | }
117 | }
118 | if (nowAnim.SwapAnim.PositionPlayer != Vector3.zero)
119 | {
120 | if (!Utilities.IsNewPosition(_player))
121 | {
122 | GetMoveController(_player).ResetPosition();
123 | }
124 | }
125 | }
126 | }
127 |
128 | var nextAnim = new AnimationInfo(_nextAinmInfo);
129 | if (nextAnim != null)
130 | {
131 | // Move characters
132 | if (Utilities.HasMovement(nextAnim))
133 | {
134 | // The position of characters as set by the current
135 | // animation pose
136 | position = hSceneTraverse
137 | .Field("nowHpointDataPos").Value;
138 |
139 | Utilities.SetOriginalPositionAll(position);
140 | if (nextAnim.SwapAnim.PositionHeroine != Vector3.zero)
141 | {
142 | GetMoveController(_heroine)
143 | .Move(nextAnim.SwapAnim.PositionHeroine);
144 | }
145 | if (nextAnim.SwapAnim.PositionPlayer != Vector3.zero)
146 | {
147 | GetMoveController(_player)
148 | .Move(nextAnim.SwapAnim.PositionPlayer);
149 | }
150 | }
151 | // Save used animation
152 | if (nextAnim.IsAnimationLoader)
153 | {
154 | _usedAnimations.Keys.Add(nextAnim.Key);
155 | }
156 | }
157 | }
158 | }
159 | catch (Exception e)
160 | {
161 | Log.Error($"0008: Error={e}");
162 | }
163 | #endif
164 | }
165 |
166 | internal static Func SiruPaste = x => x == string.Empty ?
167 | $"None" : $"{x}";
168 |
169 | ///
170 | /// Swap animation if found in mapping dictionary
171 | ///
172 | ///
173 | ///
174 | [HarmonyPostfix]
175 | [HarmonyPatch(typeof(HSceneProc), nameof(HSceneProc.ChangeAnimator))]
176 | private static void SwapAnimation(
177 | object __instance,
178 | HSceneProc.AnimationListInfo _nextAinmInfo)
179 | {
180 | if (!swapAnimationMapping.TryGetValue(
181 | _nextAinmInfo, out var swapAnimationInfo))
182 | {
183 | return;
184 | }
185 |
186 | RuntimeAnimatorController femaleCtrl = null;
187 | RuntimeAnimatorController female1Ctrl = null;
188 | RuntimeAnimatorController maleCtrl = null;
189 |
190 | if (!string.IsNullOrEmpty(swapAnimationInfo.PathFemale)
191 | || !string.IsNullOrEmpty(swapAnimationInfo.ControllerFemale))
192 | {
193 | femaleCtrl = AssetBundleManager.LoadAsset(
194 | swapAnimationInfo.PathFemale,
195 | swapAnimationInfo.ControllerFemale,
196 | typeof(RuntimeAnimatorController)).GetAsset();
197 | }
198 |
199 | // Third wheel
200 | if (!string.IsNullOrEmpty(swapAnimationInfo.PathFemale1)
201 | || !string.IsNullOrEmpty(swapAnimationInfo.ControllerFemale1))
202 | {
203 | female1Ctrl = AssetBundleManager.LoadAsset(
204 | swapAnimationInfo.PathFemale1,
205 | swapAnimationInfo.ControllerFemale1,
206 | typeof(RuntimeAnimatorController)).GetAsset();
207 | }
208 |
209 | if (!string.IsNullOrEmpty(swapAnimationInfo.PathMale)
210 | || !string.IsNullOrEmpty(swapAnimationInfo.ControllerMale))
211 | {
212 | maleCtrl = AssetBundleManager.LoadAsset(
213 | swapAnimationInfo.PathMale,
214 | swapAnimationInfo.ControllerMale,
215 | typeof(RuntimeAnimatorController)).GetAsset();
216 | }
217 | var t_hsp = Traverse.Create(__instance);
218 | var lstFemale = t_hsp.Field>("lstFemale").Value;
219 | var female = lstFemale[0];
220 | var female1 = ((lstFemale.Count > 1) ? lstFemale[1] : null);
221 | var male = t_hsp.Field("male").Value;
222 | ////TODO: male1
223 |
224 | if (femaleCtrl != null)
225 | {
226 | female.animBody.runtimeAnimatorController = SetupAnimatorOverrideController(
227 | female.animBody.runtimeAnimatorController,
228 | femaleCtrl);
229 | }
230 | if ((female1Ctrl != null)
231 | && female1 != null)
232 | {
233 | female1.animBody.runtimeAnimatorController = SetupAnimatorOverrideController(
234 | female1.animBody.runtimeAnimatorController,
235 | female1Ctrl);
236 | }
237 | if (maleCtrl != null)
238 | {
239 | male.animBody.runtimeAnimatorController = SetupAnimatorOverrideController(
240 | male.animBody.runtimeAnimatorController, maleCtrl);
241 | }
242 | // Temporarily disable in KK giving problem for some users TODO: More testing
243 | SetupMotionIK(__instance, swapAnimationInfo, _nextAinmInfo);
244 | }
245 | }
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/AnimationClipsCache.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Save key of used animations
3 | //
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Runtime.Serialization;
8 |
9 | using BepInEx;
10 |
11 | namespace AnimationLoader
12 | {
13 | [DataContract(Name = "AnimationLoader", Namespace = "https://gitgoon.dev/IllusionMods/AnimationLoader")]
14 | public class AnimationClipsCache
15 | {
16 | [DataMember]
17 | public Dictionary> Clips { set; get; }
18 |
19 | private static readonly string _path = Path.Combine(Paths.ConfigPath, "AnimationLoader");
20 | private static readonly string _fileName = $"{_path}/animationClips.cache";
21 | private static readonly DataContractSerializer _serializer = new(typeof(AnimationClipsCache));
22 | private static readonly FileInfo _fileInfo = new(_fileName);
23 |
24 | public AnimationClipsCache()
25 | {
26 | Clips = [];
27 | Clips.Clear();
28 | }
29 |
30 | public void Save()
31 | {
32 | _fileInfo.Directory.Create();
33 | var writer = new FileStream(_fileName, FileMode.Create, FileAccess.Write);
34 | _serializer.WriteObject(writer, this);
35 | writer.Close();
36 | }
37 |
38 | public void Read()
39 | {
40 | if (_fileInfo.Exists)
41 | {
42 | using var fileStream = File.Open(_fileName, FileMode.Open, FileAccess.Read);
43 | var tmp = _serializer.ReadObject(fileStream) as AnimationClipsCache;
44 | fileStream.Close();
45 |
46 | Clips = tmp?.Clips;
47 |
48 | }
49 | }
50 | }
51 |
52 | [DataContract]
53 | public class AnimationClipsByType
54 | {
55 | [DataMember]
56 | public Dictionary> Clips { set; get; }
57 |
58 | private static readonly string _path = Path.Combine(Paths.ConfigPath, "AnimationLoader");
59 | private static readonly string _fileName = $"{_path}/animationClipsByType.xml";
60 | private static readonly DataContractSerializer _serializer = new(typeof(AnimationClipsByType));
61 | private static readonly FileInfo _fileInfo = new(_fileName);
62 |
63 | public AnimationClipsByType()
64 | {
65 | Clips = [];
66 | Clips.Clear();
67 | }
68 |
69 | public void Save()
70 | {
71 | _fileInfo.Directory.Create();
72 | var writer = new FileStream(_fileName, FileMode.Create, FileAccess.Write);
73 | _serializer.WriteObject(writer, this);
74 | writer.Close();
75 | }
76 |
77 | public void Read()
78 | {
79 | if (_fileInfo.Exists)
80 | {
81 | try
82 | {
83 | var reader = new FileStream(_fileName, FileMode.Open, FileAccess.Read);
84 | var tmp = _serializer.ReadObject(reader) as AnimationClipsByType;
85 | reader.Close();
86 |
87 | Clips = tmp?.Clips;
88 | }
89 | catch (Exception ex)
90 | {
91 | Log.Error($"[AnimationClipsByType] Reading Error: {ex}");
92 | }
93 | }
94 | }
95 | }
96 |
97 | ///
98 | /// The clip names are the same in each animation for female and male.
99 | /// Every List of clip names are the same by type only three
100 | /// are needed.
101 | ///
102 | /// 1- houshi kind Hand
103 | /// 2- houshi kind Mouth
104 | /// 3- houshi kind Breasts
105 | /// 4- sonyu
106 | ///
107 | /// In the case of houshi for Hand and Breasts the list is the same. I leave
108 | /// them separate for clarity.
109 | ///
110 | public static class AnimationClips
111 | {
112 | public static readonly Dictionary> Clips = new() {
113 | { "houshi-Hand", new List {
114 | "L_Idle",
115 | "L_OLoop",
116 | "L_OUT_A",
117 | "L_OUT_Loop",
118 | "L_OUT_Start",
119 | "L_SLoop1",
120 | "L_SLoop2",
121 | "L_Stop_Idle",
122 | "L_WLoop1",
123 | "L_WLoop2",
124 | "M_Idle",
125 | "M_OLoop",
126 | "M_OUT_A",
127 | "M_OUT_Loop",
128 | "M_OUT_Start",
129 | "M_SLoop1",
130 | "M_SLoop2",
131 | "M_Stop_Idle",
132 | "M_WLoop1",
133 | "M_WLoop2",
134 | "S_Idle",
135 | "S_OLoop",
136 | "S_OUT_A",
137 | "S_OUT_Loop",
138 | "S_OUT_Start",
139 | "S_SLoop1",
140 | "S_SLoop2",
141 | "S_Stop_Idle",
142 | "S_WLoop1",
143 | "S_WLoop2" }
144 | },
145 | { "houshi-Mouth", new List {
146 | "L_Drink",
147 | "L_Drink_A",
148 | "L_Drink_IN",
149 | "L_Idle",
150 | "L_IN_Loop",
151 | "L_IN_Start",
152 | "L_OLoop",
153 | "L_Oral_Idle",
154 | "L_Oral_Idle_IN",
155 | "L_OUT_A",
156 | "L_OUT_Loop",
157 | "L_OUT_Start",
158 | "L_SLoop1",
159 | "L_SLoop2",
160 | "L_Stop_Idle",
161 | "L_Vomit",
162 | "L_Vomit_A",
163 | "L_Vomit_IN",
164 | "L_WLoop1",
165 | "L_WLoop2",
166 | "M_Drink",
167 | "M_Drink_A",
168 | "M_Drink_IN",
169 | "M_Idle",
170 | "M_IN_Loop",
171 | "M_IN_Start",
172 | "M_OLoop",
173 | "M_Oral_Idle",
174 | "M_Oral_Idle_IN",
175 | "M_OUT_A",
176 | "M_OUT_Loop",
177 | "M_OUT_Start",
178 | "M_SLoop1",
179 | "M_SLoop2",
180 | "M_Stop_Idle",
181 | "M_Vomit",
182 | "M_Vomit_A",
183 | "M_Vomit_IN",
184 | "M_WLoop1",
185 | "M_WLoop2",
186 | "S_Drink",
187 | "S_Drink_A",
188 | "S_Drink_IN",
189 | "S_Idle",
190 | "S_IN_Loop",
191 | "S_IN_Start",
192 | "S_OLoop",
193 | "S_Oral_Idle",
194 | "S_Oral_Idle_IN",
195 | "S_OUT_A",
196 | "S_OUT_Loop",
197 | "S_OUT_Start",
198 | "S_SLoop1",
199 | "S_SLoop2",
200 | "S_Stop_Idle",
201 | "S_Vomit",
202 | "S_Vomit_A",
203 | "S_Vomit_IN",
204 | "S_WLoop1",
205 | "S_WLoop2" }
206 | },
207 | { "houshi-Breasts", new List {
208 | "L_Idle",
209 | "L_OLoop",
210 | "L_OUT_A",
211 | "L_OUT_Loop",
212 | "L_OUT_Start",
213 | "L_SLoop1",
214 | "L_SLoop2",
215 | "L_Stop_Idle",
216 | "L_WLoop1",
217 | "L_WLoop2",
218 | "M_Idle",
219 | "M_OLoop",
220 | "M_OUT_A",
221 | "M_OUT_Loop",
222 | "M_OUT_Start",
223 | "M_SLoop1",
224 | "M_SLoop2",
225 | "M_Stop_Idle",
226 | "M_WLoop1",
227 | "M_WLoop2",
228 | "S_Idle",
229 | "S_OLoop",
230 | "S_OUT_A",
231 | "S_OUT_Loop",
232 | "S_OUT_Start",
233 | "S_SLoop1",
234 | "S_SLoop2",
235 | "S_Stop_Idle",
236 | "S_WLoop1",
237 | "S_WLoop2" }
238 | },
239 | { "sonyu", new List {
240 | "L_Drop",
241 | "L_Idle",
242 | "L_IN_A",
243 | "L_Insert",
244 | "L_InsertIdle",
245 | "L_M_IN_Loop",
246 | "L_M_IN_Start",
247 | "L_M_OUT_Loop",
248 | "L_M_OUT_Start",
249 | "L_OLoop",
250 | "L_OUT_A",
251 | "L_Pull",
252 | "L_SF_IN_Loop",
253 | "L_SF_IN_Start",
254 | "L_SLoop1",
255 | "L_SLoop2",
256 | "L_SS_IN_A",
257 | "L_SS_IN_Loop",
258 | "L_SS_IN_Start",
259 | "L_WF_IN_Loop",
260 | "L_WF_IN_Start",
261 | "L_WLoop1",
262 | "L_WLoop2",
263 | "L_WS_IN_A",
264 | "L_WS_IN_Loop",
265 | "L_WS_IN_Start",
266 | "M_Drop",
267 | "M_Idle",
268 | "M_IN_A",
269 | "M_Insert",
270 | "M_InsertIdle",
271 | "M_M_IN_Loop",
272 | "M_M_IN_Start",
273 | "M_M_OUT_Loop",
274 | "M_M_OUT_Start",
275 | "M_OLoop",
276 | "M_OUT_A",
277 | "M_Pull",
278 | "M_SF_IN_Loop",
279 | "M_SF_IN_Start",
280 | "M_SLoop1",
281 | "M_SLoop2",
282 | "M_SS_IN_A",
283 | "M_SS_IN_Loop",
284 | "M_SS_IN_Start",
285 | "M_WF_IN_Loop",
286 | "M_WF_IN_Start",
287 | "M_WLoop1",
288 | "M_WLoop2",
289 | "M_WS_IN_A",
290 | "M_WS_IN_Loop",
291 | "M_WS_IN_Start",
292 | "S_Drop",
293 | "S_Idle",
294 | "S_IN_A",
295 | "S_Insert",
296 | "S_InsertIdle",
297 | "S_M_IN_Loop",
298 | "S_M_IN_Start",
299 | "S_M_OUT_Loop",
300 | "S_M_OUT_Start",
301 | "S_OLoop",
302 | "S_OUT_A",
303 | "S_Pull",
304 | "S_SF_IN_Loop",
305 | "S_SF_IN_Start",
306 | "S_SLoop1",
307 | "S_SLoop2",
308 | "S_SS_IN_A",
309 | "S_SS_IN_Loop",
310 | "S_SS_IN_Start",
311 | "S_WF_IN_Loop",
312 | "S_WF_IN_Start",
313 | "S_WLoop1",
314 | "S_WLoop2",
315 | "S_WS_IN_A",
316 | "S_WS_IN_Loop",
317 | "S_WS_IN_Start",
318 | "L_A_Drop",
319 | "L_A_Idle",
320 | "L_A_IN_A",
321 | "L_A_Insert",
322 | "L_A_InsertIdle",
323 | "L_A_M_IN_Loop",
324 | "L_A_M_IN_Start",
325 | "L_A_M_OUT_A",
326 | "L_A_M_OUT_Loop",
327 | "L_A_M_OUT_Start",
328 | "L_A_OLoop",
329 | "L_A_Pull",
330 | "L_A_SF_IN_Loop",
331 | "L_A_SF_IN_Start",
332 | "L_A_SLoop1",
333 | "L_A_SLoop2",
334 | "L_A_SS_IN_A",
335 | "L_A_SS_IN_Loop",
336 | "L_A_SS_IN_Start",
337 | "L_A_WF_IN_Loop",
338 | "L_A_WF_IN_Start",
339 | "L_A_WLoop1",
340 | "L_A_WLoop2",
341 | "L_A_WS_IN_A",
342 | "L_A_WS_IN_Loop",
343 | "L_A_WS_IN_Start",
344 | "M_A_Drop",
345 | "M_A_Idle",
346 | "M_A_IN_A",
347 | "M_A_Insert",
348 | "M_A_InsertIdle",
349 | "M_A_M_IN_Loop",
350 | "M_A_M_IN_Start",
351 | "M_A_M_OUT_A",
352 | "M_A_M_OUT_Loop",
353 | "M_A_M_OUT_Start",
354 | "M_A_OLoop",
355 | "M_A_Pull",
356 | "M_A_SF_IN_Loop",
357 | "M_A_SF_IN_Start",
358 | "M_A_SLoop1",
359 | "M_A_SLoop2",
360 | "M_A_SS_IN_A",
361 | "M_A_SS_IN_Loop",
362 | "M_A_SS_IN_Start",
363 | "M_A_WF_IN_Loop",
364 | "M_A_WF_IN_Start",
365 | "M_A_WLoop1",
366 | "M_A_WLoop2",
367 | "M_A_WS_IN_A",
368 | "M_A_WS_IN_Loop",
369 | "M_A_WS_IN_Start",
370 | "S_A_Drop",
371 | "S_A_Idle",
372 | "S_A_IN_A",
373 | "S_A_Insert",
374 | "S_A_InsertIdle",
375 | "S_A_M_IN_Loop",
376 | "S_A_M_IN_Start",
377 | "S_A_M_OUT_A",
378 | "S_A_M_OUT_Loop",
379 | "S_A_M_OUT_Start",
380 | "S_A_OLoop",
381 | "S_A_Pull",
382 | "S_A_SF_IN_Loop",
383 | "S_A_SF_IN_Start",
384 | "S_A_SLoop1",
385 | "S_A_SLoop2",
386 | "S_A_SS_IN_A",
387 | "S_A_SS_IN_Loop",
388 | "S_A_SS_IN_Start",
389 | "S_A_WF_IN_Loop",
390 | "S_A_WF_IN_Start",
391 | "S_A_WLoop1",
392 | "S_A_WLoop2",
393 | "S_A_WS_IN_A",
394 | "S_A_WS_IN_Loop",
395 | "S_A_WS_IN_Start" }
396 | }
397 | };
398 | }
399 | }
400 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Utils/Utilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 |
6 | using UnityEngine;
7 |
8 | using Illusion.Extensions;
9 |
10 | using KKAPI;
11 | using KKAPI.Utilities;
12 |
13 |
14 | namespace AnimationLoader
15 | {
16 | public partial class SwapAnim
17 | {
18 | internal class Utilities
19 | {
20 | private static readonly byte _alpha = 255;
21 | private static readonly bool _isSunshine = (KoikatuAPI.GameProcessName == "KoikatsuSunshine")
22 | || (KoikatuAPI.VRProcessName == "KoikatsuSunshine_VR");
23 |
24 | private static readonly bool _isSunshineEx = _isSunshine &&
25 | (KoikatuAPI.GetGameVersion().CompareTo(new Version("1.0.8")) > 0);
26 |
27 | #region Color properties
28 | #pragma warning disable IDE1006 // Naming Styles
29 |
30 | public static Color red => new Color32(255, 0, 0, _alpha);
31 | public static Color darkred => new Color32(139, 0, 0, _alpha);
32 |
33 | public static Color magenta => new Color32(255, 0, 255, _alpha);
34 | public static Color darkmagenta => new Color32(139, 0, 139, _alpha);
35 | public static Color pink => new Color32(255, 192, 203, _alpha);
36 |
37 | public static Color yellow => new Color32(255, 255, 0, _alpha);
38 | public static Color lightyellow => new Color32(255, 255, 224, _alpha);
39 |
40 | public static Color blue => new Color32(0, 0, 255, _alpha);
41 | public static Color darkblue => new Color32(0, 0, 139, _alpha);
42 |
43 | public static Color cyan => new Color32(0, 255, 255, _alpha);
44 | public static Color darkcyan => new Color32(0, 139, 139, _alpha);
45 |
46 | public static Color orange => new Color32(255, 165, 0, _alpha);
47 | public static Color darkorange => new Color32(255, 140, 0, _alpha);
48 | public static Color gold => new Color32(255, 215, 0, _alpha);
49 |
50 | public static Color green => new Color32(0, 128, 0, _alpha);
51 | public static Color darkgreen => new Color32(0, 100, 0, _alpha);
52 | public static Color lime => new Color32(0, 255, 0, _alpha);
53 |
54 | public static Color violet => new Color32(238, 130, 238, _alpha);
55 | public static Color darkviolet => new Color32(148, 0, 211, _alpha);
56 |
57 | public static Color orangered => new Color32(255, 69, 0, _alpha);
58 | public static Color blueviolet => new Color32(138, 43, 226, _alpha);
59 | public static Color greenyellow => new Color32(173, 255, 47, _alpha);
60 | public static Color yellowgreen => new Color32(154, 205, 50, _alpha);
61 |
62 | public static Color black => new Color32(0, 0, 0, _alpha);
63 | public static Color gray => new Color32(128, 128, 128, _alpha);
64 | public static Color white => new Color32(255, 255, 255, _alpha);
65 |
66 | #pragma warning restore IDE1006 // Naming Styles
67 | #endregion
68 |
69 | public static bool IsSunshine => _isSunshine;
70 | public static bool IsSunshineEx => _isSunshineEx;
71 |
72 | ///
73 | /// Save information for template.xml
74 | ///
75 | internal static void SaveAnimInfo(List[] lstAnimInfo)
76 | {
77 | var total = 0;
78 |
79 | // id, mode,
80 | // nameAnimation (Japanese name), path, posture,
81 | // numCtrl, kindHoshi,
82 | // hoshiLoopActionS, isFemaleInitiative,
83 | // {category list}, fileSiruPaste
84 | // dicExpTaii[mode][id]
85 | for (var i = 0; i < lstAnimInfo.Length; i++)
86 | {
87 | FileInfo file = new($"lst{i}.csv");
88 |
89 | if (file.Exists)
90 | {
91 | continue;
92 | }
93 |
94 | var lines = lstAnimInfo[i].Select(x => $"{x.id}, {x.mode}," +
95 | $" {TranslateName(x.nameAnimation, true)}, {x.paramFemale.path.file}," +
96 | $" {x.paramFemale.fileMotionNeck}, {x.posture}," +
97 | $" {x.numCtrl}, {x.kindHoushi}," +
98 | $" {x.houshiLoopActionS}, {x.isFemaleInitiative}," +
99 | $"{CategoryList(x.lstCategory, true)}," +
100 | #if KKS
101 | $" {x.paramFemale.fileSiruPaste}," +
102 | $" {GetExpTaii((int)x.mode, x.id)}");
103 | #else
104 | $" {x.paramFemale.fileSiruPaste}");
105 | #endif
106 | File.WriteAllLines($"lst{i}.csv", lines.ToArray());
107 | total += lines.ToArray().Length;
108 | }
109 | }
110 |
111 | internal static string Translate(string name)
112 | {
113 | if (!TranslationHelper.TryTranslate(name, out var tmp))
114 | {
115 | return name;
116 | }
117 |
118 | return tmp;
119 | }
120 |
121 | internal static string TranslateName(string animationName, bool original = false)
122 | {
123 | var tmp = Translate(animationName);
124 | if ((tmp == animationName) || !original)
125 | {
126 | return tmp;
127 | }
128 | return $"{tmp} ({animationName})";
129 | }
130 |
131 | ///
132 | /// Return categories in the string form "{ cat 1, cat 2, ,,,}"
133 | ///
134 | ///
135 | ///
136 | ///
137 | ///
138 | internal static string CategoryList(List categories, bool names = false, bool quotes = true)
139 | {
140 | var tmp = "";
141 | var first = true;
142 |
143 | foreach (var c in categories)
144 | {
145 | if (first)
146 | {
147 | if (names)
148 | {
149 | tmp += (PositionCategory)c.category;
150 | }
151 | else
152 | {
153 | tmp += c.category.ToString();
154 | }
155 | first = false;
156 | }
157 | else
158 | {
159 | if (names)
160 | {
161 | tmp += ", " + (PositionCategory)c.category;
162 | }
163 | else
164 | {
165 | tmp += ", " + c.category.ToString();
166 | }
167 | }
168 | }
169 | return quotes ? "\" { " + tmp + " }\"" : "{ " + tmp + " }";
170 | }
171 |
172 | ///
173 | /// Ditto
174 | ///
175 | ///
176 | ///
177 | ///
178 | ///
179 | internal static string CategoryList(List categories, bool names = false, bool quotes = true)
180 | {
181 | var tmp = "";
182 | var first = true;
183 |
184 | foreach (var c in categories)
185 | {
186 | if (first)
187 | {
188 | if (names)
189 | {
190 | tmp += (PositionCategory)c;
191 | }
192 | else
193 | {
194 | tmp += c.ToString();
195 | }
196 | first = false;
197 | }
198 | else
199 | {
200 | if (names)
201 | {
202 | tmp += ", " + (PositionCategory)c;
203 | }
204 | else
205 | {
206 | tmp += ", " + c.ToString();
207 | }
208 | }
209 | }
210 | return quotes ? "\" { " + tmp + " }\"" : "{ " + tmp + " }";
211 | }
212 |
213 | internal static int CountAnimations(List[] lstAnimInfo)
214 | {
215 | var count = 0;
216 |
217 | foreach (var c in lstAnimInfo)
218 | {
219 | count += c.Count;
220 | }
221 | return count;
222 | }
223 |
224 | internal static bool HasMovement(AnimationInfo anim)
225 | {
226 | if (anim?.SwapAnim != null)
227 | {
228 | if (anim.SwapAnim.PositionHeroine != Vector3.zero)
229 | {
230 | return true;
231 | }
232 | if (anim.SwapAnim.PositionPlayer != Vector3.zero)
233 | {
234 | return true;
235 | }
236 | }
237 | return false;
238 | }
239 |
240 | ///
241 | /// Set new original position for characters if there is a move
242 | /// from original position saved
243 | ///
244 | ///
245 | internal static void SetOriginalPositionAll(Vector3 position)
246 | {
247 | if (_flags == null)
248 | {
249 | return;
250 | }
251 |
252 | var heroines = _flags.lstHeroine;
253 | for (var i = 0; i < heroines.Count; i++)
254 | {
255 | if (IsNewPosition(heroines[i].chaCtrl))
256 | {
257 | GetMoveController(heroines[i].chaCtrl).SetOriginalPosition(position);
258 | }
259 | }
260 | if (IsNewPosition(_flags.player.chaCtrl))
261 | {
262 | GetMoveController(_flags.player.chaCtrl).SetOriginalPosition(position);
263 | }
264 | }
265 |
266 | ///
267 | /// Determine if there is a change in original position
268 | ///
269 | ///
270 | ///
271 | internal static bool IsNewPosition(ChaControl chaControl)
272 | {
273 | var controller = GetMoveController(chaControl);
274 | var newPosition = chaControl.transform.position;
275 | var originalPosition = controller._originalPosition;
276 | var lastMovePosition = controller._lastMovePosition;
277 | if (newPosition != originalPosition && newPosition != lastMovePosition)
278 | {
279 | return true;
280 | }
281 | return false;
282 | }
283 | #if KKS
284 | ///
285 | /// Returns the experience level needed for the animation to be active using cached
286 | /// dictionary. The system dictionary may be altered.
287 | ///
288 | ///
289 | ///
290 | ///
291 | internal static int GetExpTaii(int mode, int id)
292 | {
293 | if (_dicExpAddTaii != null)
294 | {
295 | if (_dicExpAddTaii.ContainsKey(mode) && _dicExpAddTaii[mode].ContainsKey(id))
296 | {
297 | return _dicExpAddTaii[mode][id];
298 | }
299 | return 0;
300 | }
301 | return -1;
302 | }
303 |
304 | internal static int GetExpTaiiT(int mode, int id)
305 | {
306 | if (_dicExpAddTaii != null)
307 | {
308 | if (_dicExpAddTaii.TryGetValue(mode, out var dID))
309 | {
310 | if (dID.TryGetValue(id, out var idValue))
311 | {
312 | return idValue;
313 | }
314 | }
315 | return 0;
316 | }
317 | return -1;
318 | }
319 |
320 | ///
321 | /// Save ExpTaii in dictionary
322 | ///
323 | internal static void AlDicExpAddTaii()
324 | {
325 | _alDicExpAddTaii.Clear();
326 |
327 | foreach (var mode in animationDict.Keys)
328 | {
329 | foreach(var anim in animationDict[mode])
330 | {
331 | if(!_alDicExpAddTaii.ContainsKey(anim.Guid))
332 | {
333 | _alDicExpAddTaii.Add(anim.Guid, []);
334 | _alDicExpAddTaii[anim.Guid].Clear();
335 | }
336 | if(!_alDicExpAddTaii[anim.Guid].ContainsKey((int)anim.Mode))
337 | {
338 | _alDicExpAddTaii[anim.Guid].Add((int)anim.Mode, []);
339 | _alDicExpAddTaii[anim.Guid][(int)anim.Mode].Clear();
340 | }
341 | _alDicExpAddTaii[anim.Guid][(int)anim.Mode]
342 | .Add($"{anim.ControllerFemale}{anim.StudioId}", anim.ExpTaii);
343 | }
344 | }
345 | }
346 | #endif
347 | }
348 | }
349 | }
350 |
--------------------------------------------------------------------------------
/src/AnimationLoader.Core/Hooks.AnimationLists.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text;
4 |
5 | using Illusion.Extensions;
6 |
7 | using BepInEx.Logging;
8 | using HarmonyLib;
9 |
10 | namespace AnimationLoader
11 | {
12 | public partial class SwapAnim
13 | {
14 | #if KKS
15 | internal static UsedAnimations _usedAnimations = new();
16 | #endif
17 | internal static AnimationsUseStats _animationsUseStats = new();
18 |
19 | internal partial class Hooks
20 | {
21 | internal static bool once = false;
22 | private static List[] _gameAnimations =
23 | new List[8];
24 |
25 | ///
26 | /// Add new animations to lstAnimInfo (list of all animations available)
27 | /// aibu and sonyu
28 | ///
29 | ///
30 | [HarmonyPostfix]
31 | [HarmonyPatch(typeof(HSceneProc), nameof(HSceneProc.CreateAllAnimationList))]
32 | private static void ExtendList(object __instance)
33 | {
34 | var hSceneTraverse = Traverse.Create(__instance);
35 | var addedAnimations = new StringBuilder();
36 | var lstAnimInfo = hSceneTraverse
37 | .Field[]>("lstAnimInfo").Value;
38 |
39 | var countGA = 0;
40 | var countAL = 0;
41 | var strTmp = string.Empty;
42 | countGA = Utilities.CountAnimations(lstAnimInfo);
43 | #if DEBUG
44 | Utilities.SaveAnimInfo(lstAnimInfo);
45 | #endif
46 | swapAnimationMapping =
47 | [];
48 |
49 | _gameAnimations = lstAnimInfo;
50 |
51 | if (animationDict.Count > 0)
52 | {
53 | foreach (var anim in animationDict.SelectMany(
54 | e => e.Value,
55 | (e, a) => a
56 | ))
57 | {
58 | var mode = (int)anim.Mode;
59 | if (mode < 0 || mode >= lstAnimInfo.Length)
60 | {
61 | continue;
62 | }
63 | var animListInfo = lstAnimInfo[(int)anim.Mode];
64 |
65 | var donorInfo = animListInfo
66 | .FirstOrDefault(x => x.id == anim.DonorPoseId)?.DeepCopy();
67 |
68 | if (donorInfo == null)
69 | {
70 | Log.Level(LogLevel.Error, $"0009: No donor found: " +
71 | $"mode={anim.Mode} DonorPoseId={anim.DonorPoseId} " +
72 | $"animation={anim.AnimationName}");
73 | continue;
74 | }
75 | //
76 | // There are cases where one Id works for the female but not the
77 | // male and vice-versa
78 | //
79 | // Female donor
80 | #if KK
81 | // Don't touch KK for now treat NeckDonorId like previous version
82 | // only apply it to the female
83 | if (anim.NeckDonorId >= 0 && anim.NeckDonorId != anim.DonorPoseId)
84 | {
85 | // PR #23 Change to Log.Level to always show log,
86 | // update log ID's use temp variable to add log to log list
87 | var newNeckDonor = animListInfo
88 | .FirstOrDefault(x => x.id == anim.NeckDonorId);
89 | if (newNeckDonor == null)
90 | {
91 | strTmp = $"0029: Invalid or missing " +
92 | $"NeckDonorId: mode={anim.Mode} " +
93 | $"NeckDonorId={anim.NeckDonorId}";
94 | Log.Level(LogLevel.Warning, strTmp);
95 | addedAnimations.Append(strTmp);
96 | }
97 | else
98 | {
99 | var newMotionNeck =
100 | newNeckDonor?.paramFemale?.fileMotionNeck;
101 | if (newMotionNeck == null)
102 | {
103 | strTmp = $"0030: NeckDonorId didn't point to" +
104 | $" a usable fileMotionNeck: " +
105 | $"mode={anim.Mode} NeckDonorId={anim.NeckDonorId}";
106 | Log.Level(LogLevel.Warning, strTmp);
107 | addedAnimations.Append(strTmp);
108 | }
109 | else
110 | {
111 | donorInfo.paramFemale.fileMotionNeck = newMotionNeck;
112 | }
113 | }
114 | }
115 | #else
116 | if (anim.NeckDonorIdFemale >= 0 && anim.NeckDonorIdFemale != anim.DonorPoseId)
117 | {
118 | // PR #23 Change to Log.Level to always show log, update log ID's
119 | // use temp variable to add log to log list
120 | var newNeckDonor = animListInfo
121 | .FirstOrDefault(x => x.id == anim.NeckDonorIdFemale);
122 | if (newNeckDonor == null)
123 | {
124 | strTmp = $"0029: Invalid or missing " +
125 | $"NeckDonorId: mode={anim.Mode} " +
126 | $"NeckDonorId={anim.NeckDonorIdFemale} " +
127 | $"Animation={anim.AnimationName}";
128 | Log.Level(LogLevel.Warning, strTmp);
129 | addedAnimations.Append(strTmp);
130 | }
131 | else
132 | {
133 | var newMotionNeck = newNeckDonor?.paramFemale?.fileMotionNeck;
134 | if (newMotionNeck == null)
135 | {
136 | strTmp = $"0030: NeckDonorId didn't point to" +
137 | $" a usable fileMotionNeck: " +
138 | $"mode={anim.Mode} NeckDonorId={anim.NeckDonorIdFemale} " +
139 | $"Animation={anim.AnimationName}";
140 | Log.Level(LogLevel.Warning, strTmp);
141 | addedAnimations.Append(strTmp);
142 | }
143 | else
144 | {
145 | donorInfo.paramFemale.fileMotionNeck = newMotionNeck;
146 | }
147 | }
148 | }
149 | #endif
150 | // Female1 donor
151 | if (anim.ControllerFemale1 != null)
152 | {
153 | if (anim.NeckDonorIdFemale1 >= 0
154 | && anim.NeckDonorIdFemale1 != anim.DonorPoseId)
155 | {
156 | var newNeckDonor = animListInfo
157 | .FirstOrDefault(x => x.id == anim.NeckDonorIdFemale1);
158 | if (newNeckDonor == null)
159 | {
160 | strTmp = $"0029: Invalid or missing " +
161 | $"NeckDonorId: mode={anim.Mode} " +
162 | $"NeckDonorId={anim.NeckDonorIdFemale1} " +
163 | $"Animation={anim.AnimationName}";
164 | Log.Level(LogLevel.Warning, strTmp);
165 | addedAnimations.Append(strTmp);
166 | }
167 | else
168 | {
169 | var newMotionNeck =
170 | newNeckDonor?.paramFemale1?.fileMotionNeck;
171 | if (newMotionNeck == null)
172 | {
173 | strTmp = $"0030: NeckDonorId didn't point to" +
174 | $" a usable fileMotionNeck: " +
175 | $"mode={anim.Mode} " +
176 | $"NeckDonorId={anim.NeckDonorIdFemale1} " +
177 | $"Animation={anim.AnimationName}";
178 | Log.Level(LogLevel.Warning, strTmp);
179 | addedAnimations.Append(strTmp);
180 | }
181 | else
182 | {
183 | donorInfo.paramFemale1.fileMotionNeck =
184 | newMotionNeck;
185 | }
186 | }
187 | }
188 | }
189 |
190 | // Male donor did not have NeckDonor applied to it
191 | // TODO: Treat NeckDonorId as global and apply it to everyone
192 | // when there no specific one must remove from manifest
193 | if (anim.NeckDonorIdMale >= 0
194 | && anim.NeckDonorIdMale != anim.DonorPoseId)
195 | {
196 | var newNeckDonor = animListInfo
197 | .FirstOrDefault(x => x.id == anim.NeckDonorIdMale);
198 | if (newNeckDonor == null)
199 | {
200 | strTmp = $"0029B: Invalid or missing " +
201 | $"NeckDonorIdMale: mode={anim.Mode} " +
202 | $"NeckDonorIdMale={anim.NeckDonorIdMale} " +
203 | $"Animation={anim.AnimationName}";
204 | Log.Level(LogLevel.Warning, strTmp);
205 | addedAnimations.Append(strTmp);
206 | }
207 | else
208 | {
209 | var newMotionNeck = newNeckDonor?.paramMale?.fileMotionNeck;
210 | if (newMotionNeck == null)
211 | {
212 | strTmp = $"0030B: NeckDonorIdMale didn't point to" +
213 | $" a usable fileMotionNeck: " +
214 | $"mode={anim.Mode} " +
215 | $"NeckDonorIdMale={anim.NeckDonorIdMale} " +
216 | $"Animation={anim.AnimationName}";
217 | Log.Level(LogLevel.Warning, strTmp);
218 | addedAnimations.Append(strTmp);
219 | }
220 | else
221 | {
222 | donorInfo.paramMale.fileMotionNeck = newMotionNeck;
223 | }
224 | }
225 | }
226 |
227 | if (anim.IsFemaleInitiative != null)
228 | {
229 | donorInfo.isFemaleInitiative = anim.IsFemaleInitiative.Value;
230 | }
231 |
232 | if (!string.IsNullOrEmpty(anim.FileSiruPaste))
233 | {
234 | // Check if FileSiruPaste is on dictionary first
235 | if (SiruPasteFiles.TryGetValue(
236 | anim.FileSiruPaste.ToLower(), out var fileSiruPaste))
237 | {
238 | donorInfo.paramFemale.fileSiruPaste = fileSiruPaste;
239 | }
240 | else
241 | {
242 | // TODO: Check that it is a valid name in
243 | // anim.FileSiruPaste
244 | donorInfo.paramFemale.fileSiruPaste =
245 | anim.FileSiruPaste.ToLower();
246 | }
247 | }
248 |
249 | // Category
250 | // int category
251 | // string fileMove
252 | donorInfo.lstCategory = anim.categories.Select(c =>
253 | {
254 | var cat = new HSceneProc.Category {
255 | category = (int)c
256 | };
257 | return cat;
258 | }).ToList();
259 |
260 | // The mode and kindHoushi is not honored
261 | if (anim.Mode == HFlag.EMode.houshi)
262 | {
263 | donorInfo.kindHoushi = (int)anim.kindHoushi;
264 | }
265 | #if KKS
266 | // Update name so it shows on button text label correctly
267 | donorInfo.nameAnimation = anim.AnimationName;
268 | #endif
269 | animListInfo.Add(donorInfo);
270 | swapAnimationMapping[donorInfo] = anim;
271 | countAL++;
272 | }
273 |
274 |
275 | }
276 |
277 | #if KKS
278 | _animationsUseStats.Init(lstAnimInfo);
279 | #endif
280 |
281 | // log footer
282 | addedAnimations.Append($"\n{countAL + countGA} animations available: Game " +
283 | $"standard = {countGA} " +
284 | $"AnimationLoader = {countAL}\n");
285 | #if DEBUG
286 | if (!once)
287 | {
288 | #if KKS
289 | Log.Warning($"0012: Added animations:\n{addedAnimations}\n\n" +
290 | $"Stats:\n{_animationsUseStats}");
291 | #else
292 | Log.Warning($"0012: Added animations:\n{addedAnimations}");
293 | #endif
294 | var logLines = new StringBuilder();
295 |
296 | foreach (var fj in _footJobAnimations)
297 | {
298 | logLines.AppendLine(fj.ToString());
299 | }
300 | Log.Warning($"0012: Foot jobs.\n{logLines}");
301 | once = true;
302 | }
303 | #else
304 | // For release log animations added
305 | if (!once)
306 | {
307 | Log.Debug($"0012: Added animations:\n{addedAnimations}");
308 | once = true;
309 | }
310 | #endif
311 | }
312 | }
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/src/AnimationLoader.KoikatsuSunshine/Hooks.LoadMotionList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using TMPro;
6 | using UnityEngine;
7 | using UnityEngine.UI;
8 |
9 | using IllusionUtility.GetUtility;
10 | using SceneAssist;
11 |
12 | using HarmonyLib;
13 |
14 |
15 | namespace AnimationLoader
16 | {
17 | public partial class SwapAnim
18 | {
19 | internal static Dictionary> _dicExpAddTaii = [];
20 | internal static Dictionary>> _alDicExpAddTaii = [];
22 |
23 | internal partial class Hooks
24 | {
25 | ///
26 | /// In order to expand more easily had to replicate the original function and
27 | /// adjust to needs.
28 | ///
29 | /// Colors:
30 | /// Enough experience
31 | /// White - game animation
32 | /// Yellow - additional animation
33 | /// Not enough experience
34 | /// Cyan - game animation
35 | /// Pink - additional animation
36 | ///
37 | /// Green - HPoint animations accessed by asking for sex
38 | ///
39 | /// TODO: Work on grid UI this is a maybe.
40 | ///
41 | ///
42 | ///
43 | ///
44 | ///
45 | [HarmonyPostfix]
46 | [HarmonyPatch(typeof(HSprite), nameof(HSprite.LoadMotionList))]
47 | private static void LoadMotionListPostfix(
48 | HSprite __instance,
49 | List _lstAnimInfo,
50 | GameObject _objParent)
51 | {
52 | var buttonParent = _objParent.transform;
53 | var hExp = __instance.flags.lstHeroine[0].hExp;
54 |
55 | if (buttonParent.childCount > 0)
56 | {
57 | // remove buttons
58 | buttonParent.Cast().ToList().ForEach(x => Destroy(x.gameObject));
59 | }
60 |
61 | if (_lstAnimInfo == null || _lstAnimInfo.Count == 0)
62 | {
63 | return;
64 | }
65 |
66 | var toggleGroup = _objParent.GetComponent();
67 | var animationOn = false;
68 | var isALAnim = false;
69 |
70 | // Loop through selected animations _lstAnimInfo only has the animations
71 | // selected according to category, experience, etc
72 | for (var index = 0; index < _lstAnimInfo.Count; ++index)
73 | {
74 | isALAnim = false;
75 | swapAnimationMapping.TryGetValue(_lstAnimInfo[index], out var swap);
76 | if (swap is not null)
77 | {
78 | isALAnim = true;
79 | if (!AnimationCheckOk(__instance, swap))
80 | {
81 | continue;
82 | }
83 | }
84 |
85 | var button = Instantiate(__instance.objMotionListNode);
86 |
87 | var animationInfoComponent =
88 | button.AddComponent();
89 |
90 | // Assign animation
91 | animationInfoComponent.info = _lstAnimInfo[index];
92 | // Assign button to parent transform
93 | button.transform.SetParent(buttonParent, false);
94 |
95 | // TextMesh of button
96 | var textMesh = button.transform.FindLoop("TextMeshPro Text");
97 |
98 | if (textMesh.TryGetComponent(out var label))
99 | {
100 | // Text label
101 | label.text = animationInfoComponent.info.nameAnimation;
102 | if (isALAnim)
103 | {
104 | // Animations added to the game
105 | label.color = Utilities.yellow;
106 |
107 | if (swap is not null)
108 | {
109 | if (HighLight.Value)
110 | {
111 | if (swap.MotionIKDonor != null)
112 | {
113 | // MotionIK information
114 | label.color = Utilities.gold;
115 | }
116 | else if((swap.ExpTaii >= 5) && (hExp < swap.ExpTaii))
117 | {
118 | // Foreground color yellow for loaded animations
119 | //label.color = Utilities.orange;
120 | label.color = Utilities.pink;
121 | }
122 | }
123 | }
124 | }
125 | else
126 | {
127 | // game animations
128 | var tmp = Utilities.GetExpTaii((int)animationInfoComponent.info.mode,
129 | animationInfoComponent.info.id);
130 | if (HighLight.Value)
131 | {
132 | // Emphasize effects of the store plug-in
133 | if ((tmp >= 50) && (hExp < tmp))
134 | {
135 | label.color = Utilities.cyan;
136 | }
137 | var c = animationInfoComponent.info.lstCategory;
138 | if (!_specialAnimation && (c[0].category is 12 or > 1000))
139 | {
140 | label.color = Utilities.lime;
141 | }
142 | }
143 | }
144 | }
145 |
146 | var toggle = button.GetComponent();
147 |
148 | if ((toggle is not null) && (toggleGroup is not null))
149 | {
150 | // Assign button to toggle group
151 | toggle.group = toggleGroup;
152 | // ?? Magic operations
153 | toggle.enabled = false;
154 | toggle.enabled = true;
155 | }
156 |
157 | // Look for New label
158 | var newLabel = button.transform.FindLoop("New");
159 |
160 | // Activate New label according to previous usage saved in playHlist
161 | if ((bool)(UnityEngine.Object)newLabel)
162 | {
163 | if (isALAnim)
164 | {
165 | // Manage new status for loaded animations it depended
166 | if (_usedAnimations.Keys.Count > 0)
167 | {
168 | newLabel.SetActive(!_usedAnimations.Keys.Contains(
169 | GetAnimationKey(animationInfoComponent.info)));
170 | }
171 | if (__instance.flags.isFreeH && EnableAllFreeH.Value)
172 | {
173 | newLabel.SetActive(false);
174 | }
175 | }
176 | else
177 | {
178 | //
179 | // This dictionary is filled with the id's of animations used at least
180 | // once by category
181 | //
182 | // playHList =
183 | // {0:[0,16,4,3,7],
184 | // 1:[1,33,32,36,59,63,17],
185 | // 2:[44,23,3,12,8,19,9,47,40,1,49,42],
186 | // ..}
187 | //
188 | var playHlist = Manager.Game.globalData.playHList;
189 |
190 | if (!playHlist.TryGetValue(
191 | (int)animationInfoComponent.info.mode, out var intSet))
192 | {
193 | // Add missing category
194 | playHlist[(int)animationInfoComponent.info.mode] =
195 | intSet = [];
196 | }
197 | // Show new if animation is not in used animation set.
198 | newLabel.SetActive(
199 | !intSet.Contains(animationInfoComponent.info.id));
200 | }
201 | }
202 |
203 | var nowPosition = button.transform.FindLoop("NowPos");
204 |
205 | if (nowPosition is not null)
206 | {
207 | // Check animation to see if it correspond to current category
208 | var foundCategory = false;
209 | foreach (var category in _lstAnimInfo[index].lstCategory)
210 | {
211 | if (__instance.lstCategory.Contains(category.category))
212 | {
213 | foundCategory = true;
214 | break;
215 | }
216 | }
217 | // Turn on change category icon if animation categories list
218 | // does not have the current category
219 | nowPosition.SetActive(!foundCategory);
220 | }
221 |
222 | // Assign actions to take on user interaction
223 | button.GetComponent().listClickAction.Add(
224 | () => __instance.OnChangePlaySelect(button));
225 | button.GetComponent().listDownAction.Add(
226 | () => __instance.OnMouseDownSlider());
227 | button.SetActive(true);
228 |
229 | // Highlight current animation
230 | if (_lstAnimInfo[index] == __instance.flags.nowAnimationInfo)
231 | {
232 | if (toggle is not null)
233 | {
234 | toggle.isOn = true;
235 | }
236 | animationOn = true;
237 | }
238 | }
239 |
240 | if (!animationOn)
241 | {
242 | for (var index = 0; index < _objParent.transform.childCount; ++index)
243 | {
244 | var componentInChildren =
245 | _objParent.transform
246 | .GetChild(index)
247 | .GetComponentInChildren();
248 | if (componentInChildren != null)
249 | {
250 | componentInChildren.OnChangeValueEnable(false);
251 | }
252 | }
253 | }
254 |
255 | // sort buttons by name
256 | if (SortPositions.Value)
257 | {
258 | var allButtons = buttonParent.Cast().OrderBy(
259 | x => x.GetComponentInChildren().text).ToList();
260 | foreach (var t in allButtons)
261 | {
262 | t.SetAsLastSibling();
263 | }
264 | }
265 | }
266 |
267 | [HarmonyPostfix]
268 | [HarmonyPatch(
269 | typeof(HSceneProc),
270 | nameof(HSceneProc.LoadAddTaii),
271 | [typeof(List)])]
272 | private static void LoadAddTaiiPostfix(
273 | object __instance, List param)
274 | {
275 | var hsceneTraverse = Traverse.Create(__instance);
276 | var dicExpAddTaii = hsceneTraverse
277 | .Field>>("dicExpAddTaii").Value;
278 |
279 | foreach (var item in dicExpAddTaii)
280 | {
281 | if (_dicExpAddTaii != null)
282 | {
283 | if (!_dicExpAddTaii.ContainsKey(item.Key))
284 | {
285 | // Save the original dictionary for testing
286 | _dicExpAddTaii.Add(
287 | item.Key, new Dictionary(item.Value));
288 | }
289 | }
290 | }
291 | }
292 |
293 | internal static bool AnimationCheckOk(HSprite hsprite, SwapAnimationInfo anim)
294 | {
295 | #if DEBUG
296 | if (TestMode.Value)
297 | {
298 | return true;
299 | }
300 | #endif
301 | if (hsprite.flags.isFreeH)
302 | {
303 | if (EnableAllFreeH.Value)
304 | {
305 | return true;
306 | }
307 | // Only show used animations in FreeH
308 | if (_usedAnimations.Keys.Count < 0)
309 | {
310 | return false;
311 | }
312 | if (!_usedAnimations.Keys.Contains(GetAnimationKey(anim)))
313 | {
314 | return false;
315 | }
316 | }
317 |
318 | #if DEBUG
319 | //if (CheckExperience(hsprite, anim) != CheckExperienceT(hsprite, anim))
320 | //{
321 | // Log.Level(BepInEx.Logging.LogLevel.Error, $"CheckExperience " +
322 | // $"original={CheckExperience(hsprite, anim)} " +
323 | // $"TryGet={CheckExperienceT(hsprite, anim)}");
324 | //}
325 | #endif
326 | if (UseAnimationLevels.Value && !CheckExperienceT(hsprite, anim))
327 | {
328 | // Not enough experience
329 | return false;
330 | }
331 | return true;
332 | }
333 |
334 | ///
335 | /// Checks heroine experience against ExpTaii of swap animation
336 | ///
337 | ///
338 | ///
339 | //internal static bool CheckExperience(HSprite hsprite, SwapAnimationInfo anim)
340 | //{
341 | // var hExp = hsprite.flags.lstHeroine[0].hExp;
342 | // var expTaii = (double)anim.ExpTaii;
343 | //
344 | // if(_alDicExpAddTaii.ContainsKey(anim.Guid))
345 | // {
346 | // if (_alDicExpAddTaii[anim.Guid].ContainsKey((int)anim.Mode)
347 | // && _alDicExpAddTaii[anim.Guid][(int)anim.Mode]
348 | // .ContainsKey($"{anim.ControllerFemale}{anim.StudioId}"))
349 | // {
350 | // expTaii = _alDicExpAddTaii[anim.Guid][(int)anim.Mode][$"{anim.ControllerFemale}{anim.StudioId}"];
351 | // }
352 | // else
353 | // {
354 | // expTaii = -1;
355 | // }
356 | // }
357 | // if ((double)hExp >= expTaii)
358 | // {
359 | // return true;
360 | // }
361 | // return false;
362 | //}
363 |
364 | ///
365 | /// Checks heroine experience against ExpTaii of swap animation
366 | ///
367 | ///
368 | ///
369 | ///
370 | internal static bool CheckExperienceT(HSprite hsprite, SwapAnimationInfo anim)
371 | {
372 | var hExp = hsprite.flags.lstHeroine[0].hExp;
373 | var expTaii = (double)anim.ExpTaii;
374 |
375 | if (_alDicExpAddTaii.TryGetValue(anim.Guid, out var animGuid))
376 | {
377 | if (animGuid.TryGetValue((int)anim.Mode, out var animMode))
378 | {
379 | if (animMode.TryGetValue($"{anim.ControllerFemale}{anim.StudioId}", out var value))
380 | {
381 | expTaii = value;
382 | }
383 | else
384 | {
385 | expTaii = -1;
386 | }
387 | }
388 | }
389 |
390 | if ((double)hExp >= expTaii)
391 | {
392 | return true;
393 | }
394 | return false;
395 | }
396 |
397 | }
398 | }
399 | }
400 |
--------------------------------------------------------------------------------