habeebweeb / COM3D2.HighHeel

COM3D2 plugin that dynamically adjusts maid's feet for high heels.
GNU General Public License v3.0
4 stars 5 forks source link

Body offset misaligns maid with man in yotogi mode #2

Open diddpro opened 3 years ago

diddpro commented 3 years ago

Hello. After I tested this plugin, I found that 'offset' make height difference with man body in yotogi mode. When a maid puts on a high heel, a men has to get same offset. Could you make this possible?

habeebweeb commented 3 years ago

This misalignment is probably prevalent in different parts of the game as well. Need to consider what should be done with multiple maids with different body offset settings.

InoryS commented 9 months ago

Not only yotogi, I found that karaoke mode also requires a different bodyoffset.

Could you please add a configuration so that different Scene use different Bodyoffsets? For example, the Scenelevel of yotogi is 14, karaoke is 65. And we can set a Bodyoffset value separately for the Scene 14 such as 0.00 and set Scene 65 to 0.08.

This way we can handle future updates without having to change the code.

habeebweeb commented 9 months ago

Good idea. I think the plan for this is to merge the changes made in fdef28d1a8dfb4975ef2e3eba255d913883f8008 into main and then move along with implementing a configurable allowlist.

I am very busy with MeidoPhotoStudio though so no promises that this will get done anytime soon.

InoryS commented 2 months ago

I tried to make a prototype based on the current code from Github, but it seems to behave differently than the precompiled version you posted.

For example, using hhmod_27d_sandals_white.menu will cause the plugin to try to read hhmod_27d_sandals.json, which the version you posted does not. I did not change these logics, so I think it is a code inconsistency.

Can you update the repository code? Or did I do something wrong.

The version I compiled is here: https://github.com/InoryS/COM3D2.HighHeel/releases It can also be found in the Github action. (It's rough because I'm so ignorant of C#)

I decompiled the dll that I current use and found out it's com.ongame.com3d2.highheel, so it doesn't match the current repo, sorry. And I found his repository, but his repository code is still not complete. There is an IndividualAngles.cs in the decompiled code, but the repository does not have this code. If I compile that repository code directly, after compiling I get plugins that behave differently. And unfortunately, the decompiled code cannot be compiled directly, so the decompiled code cannot be used. So I guess there's nothing I can do here. https://github.com/Kotone4869/COM3D2.HighHeel

Without the full code, the solution I can think of is to create another plugin that also detects shoes starting with hhmod_ and then adjusts the character coordinates.

Decompilation You can see that Hook.cs is calling IndividualAngles, so it is not redundant code. Core/IndividualAngles.cs: ``` using System; using System.Runtime.CompilerServices; namespace COM3D2.HighHeel.Core { // Token: 0x0200000D RID: 13 public class IndividualAngles { // Token: 0x17000009 RID: 9 // (get) Token: 0x06000055 RID: 85 RVA: 0x00003B95 File Offset: 0x00001D95 // (set) Token: 0x06000056 RID: 86 RVA: 0x00003B9D File Offset: 0x00001D9D public float x { get; set; } // Token: 0x1700000A RID: 10 // (get) Token: 0x06000057 RID: 87 RVA: 0x00003BA6 File Offset: 0x00001DA6 // (set) Token: 0x06000058 RID: 88 RVA: 0x00003BAE File Offset: 0x00001DAE public float y { get; set; } // Token: 0x1700000B RID: 11 // (get) Token: 0x06000059 RID: 89 RVA: 0x00003BB7 File Offset: 0x00001DB7 // (set) Token: 0x0600005A RID: 90 RVA: 0x00003BBF File Offset: 0x00001DBF public float z { get; set; } // Token: 0x0600005B RID: 91 RVA: 0x00003BC8 File Offset: 0x00001DC8 [NullableContext(1)] public IndividualAngles(float xJson, float yJson, float zJson, string boneName) { uint num = .ComputeStringHash(boneName); if (num <= 2010127224U) { if (num <= 1718386694U) { if (num != 858099551U) { if (num != 1701609075U) { if (num != 1718386694U) { return; } if (!(boneName == "toeR1")) { return; } this.x = xJson + 5.3609614f; this.y = yJson + 357.66226f; this.z = zJson + 283.45108f; return; } else { if (!(boneName == "toeR2")) { return; } this.x = xJson + 356.4189f; this.y = yJson + 1.1177053f; this.z = zJson + 286.8531f; return; } } else { if (!(boneName == "toeL11")) { return; } this.x = xJson + -6.798167E-05f; this.y = yJson + -9.544926E-05f; this.z = zJson + 5.9977658E-05f; return; } } else if (num != 1735164313U) { if (num != 1942869653U) { if (num != 2010127224U) { return; } if (!(boneName == "toeR01")) { return; } this.x = xJson + 8.0857775E-05f; this.y = yJson + 7.6974815E-05f; this.z = zJson + 9.406906f; return; } else { if (!(boneName == "toeR11")) { return; } this.x = xJson + -0.00016157667f; this.y = yJson + 3.6019292E-06f; this.z = zJson + -5.0799536E-05f; return; } } else { if (!(boneName == "toeR0")) { return; } this.x = xJson + 0.21639448f; this.y = yJson + 359.51022f; this.z = zJson + 280.0905f; return; } } else if (num <= 3955933670U) { if (num != 3072892354U) { if (num != 3274517972U) { if (num != 3955933670U) { return; } if (!(boneName == "toeR21")) { return; } this.x = xJson + 7.788669E-05f; this.y = yJson + 1.6425354E-05f; this.z = zJson + 3.886178f; return; } else { if (!(boneName == "toeL21")) { return; } this.x = xJson + 6.8601395E-05f; this.y = yJson + -2.3635866E-05f; this.z = zJson + 3.8861122f; return; } } else { if (!(boneName == "toeL01")) { return; } this.x = xJson + -0.00015392156f; this.y = yJson + 6.99606E-05f; this.z = zJson + 9.406873f; return; } } else if (num != 4206299828U) { if (num != 4223077447U) { if (num != 4256632685U) { return; } if (!(boneName == "toeL2")) { return; } this.x = xJson + 3.581009f; this.y = yJson + 358.88257f; this.z = zJson + 286.8532f; return; } else { if (!(boneName == "toeL0")) { return; } this.x = xJson + 359.78348f; this.y = yJson + 0.48969793f; this.z = zJson + 280.0905f; return; } } else { if (!(boneName == "toeL1")) { return; } this.x = xJson + 354.6391f; this.y = yJson + 2.3375845f; this.z = zJson + 283.45102f; return; } } } } ``` Core/Hook.cs: ``` using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using HarmonyLib; using UnityEngine; namespace COM3D2.HighHeel.Core { // Token: 0x0200000C RID: 12 [NullableContext(1)] [Nullable(0)] public static class Hooks { // Token: 0x0600004B RID: 75 RVA: 0x0000352C File Offset: 0x0000172C [HarmonyPostfix] [HarmonyPatch(typeof(TBodySkin), "Load", new Type[] { typeof(MPN), typeof(Transform), typeof(Transform), typeof(Dictionary), typeof(string), typeof(string), typeof(string), typeof(string), typeof(int), typeof(bool), typeof(int) })] public static void OnTBodySkinLoad(TBodySkin __instance) { if (__instance.SlotId != 14) { return; } if (Plugin.Instance == null) { return; } if (Hooks.ShoeConfigs.ContainsKey(__instance.body)) { Plugin.Instance.Logger.LogDebug("OnTBodySkinLoad: ShoeConfigs already contains " + __instance.obj.name + ". How?"); Hooks.ShoeConfigs.Remove(__instance.body); } string name = __instance.obj.name; int startIndex; if ((startIndex = name.IndexOf("hhmod_", StringComparison.Ordinal)) < 0) { return; } string text = name.Substring(startIndex, 9); if (!Plugin.Instance.ShoeDatabase.ContainsKey(text)) { Plugin.Instance.Logger.LogWarning("Configuration '" + text + "' could not be found!"); return; } Hooks.ShoeConfigs[__instance.body] = text; Plugin.Instance.ImportConfigsAndUpdate(text); } // Token: 0x0600004C RID: 76 RVA: 0x00003614 File Offset: 0x00001814 [HarmonyPostfix] [HarmonyPatch(typeof(TBody), "LateUpdate")] public static void LateUpdate(TBody __instance) { if (__instance.boMAN || !__instance.isLoadedBody) { return; } if (Plugin.Instance == null || !Plugin.Instance.Configuration.Enabled.Value) { return; } if (!__instance.GetSlotVisible(14)) { return; } if (!Plugin.IsDance && !__instance.GetAnimation().isPlaying) { return; } MaidTransforms maidTransforms; if (!Hooks.MaidTransforms.TryGetValue(__instance, out maidTransforms)) { return; } ShoeConfig editModeConfig; if (Plugin.Instance.EditMode) { editModeConfig = Plugin.Instance.EditModeConfig; } else { string key; if (!Hooks.ShoeConfigs.TryGetValue(__instance, out key)) { return; } if (!Plugin.Instance.ShoeDatabase.TryGetValue(key, out editModeConfig)) { return; } } Transform transform; Transform transform2; Transform[] array; Transform transform3; Transform transform4; Transform transform5; Transform transform6; Transform[] array2; Transform transform7; Transform transform8; Transform transform9; maidTransforms.Deconstruct(out transform, out transform2, out array, out transform3, out transform4, out transform5, out transform6, out array2, out transform7, out transform8, out transform9); Transform transform10 = transform; Transform foot = transform2; Transform[] toes = array; Transform foot2 = transform6; Transform[] toes2 = array2; float num; float num2; float num3; float num4; float num5; float num6; float num7; float num8; float num9; float num10; float num11; float num12; float num13; float num14; float num15; float num16; float num17; float num18; float num19; float num20; float num21; float num22; float num23; float num24; float num25; float num26; float num27; float num28; float num29; float num30; float num31; float num32; float num33; float num34; float num35; float num36; float num37; float num38; float num39; float num40; float num41; float num42; float num43; float num44; float num45; float num46; float num47; float num48; float num49; float num50; float num51; float num52; float num53; float num54; float num55; editModeConfig.Deconstruct(out num, out num2, out num3, out num4, out num5, out num6, out num7, out num8, out num9, out num10, out num11, out num12, out num13, out num14, out num15, out num16, out num17, out num18, out num19, out num20, out num21, out num22, out num23, out num24, out num25, out num26, out num27, out num28, out num29, out num30, out num31, out num32, out num33, out num34, out num35, out num36, out num37, out num38, out num39, out num40, out num41, out num42, out num43, out num44, out num45, out num46, out num47, out num48, out num49, out num50, out num51, out num52, out num53, out num54, out num55); float num56 = num; float angle = num2; float max = num3; float correctionAngle = num4; float xJson = num11; float xJson2 = num12; float xJson3 = num13; float xJson4 = num14; float xJson5 = num15; float xJson6 = num16; float yJson = num17; float yJson2 = num18; float yJson3 = num19; float yJson4 = num20; float yJson5 = num21; float yJson6 = num22; float zJson = num23; float zJson2 = num24; float zJson3 = num25; float zJson4 = num26; float zJson5 = num27; float zJson6 = num28; float angle2 = num29; float max2 = num30; float correctionAngle2 = num31; float xJson7 = num38; float xJson8 = num39; float xJson9 = num40; float xJson10 = num41; float xJson11 = num42; float xJson12 = num43; float yJson7 = num44; float yJson8 = num45; float yJson9 = num46; float yJson10 = num47; float yJson11 = num48; float yJson12 = num49; float zJson7 = num50; float zJson8 = num51; float zJson9 = num52; float zJson10 = num53; float zJson11 = num54; float zJson12 = num55; transform10.Translate(Vector3.up * num56, 0); Hooks.g__RotateFoot|4_0(foot, angle, max); Hooks.g__RotateFoot|4_0(foot2, angle2, max2); Hooks.g__RotateToesIndividual|4_2(toes, correctionAngle, new List { new IndividualAngles(xJson, yJson, zJson, "toeL0"), new IndividualAngles(xJson2, yJson2, zJson2, "toeL01"), new IndividualAngles(xJson3, yJson3, zJson3, "toeL1"), new IndividualAngles(xJson4, yJson4, zJson4, "toeL11"), new IndividualAngles(xJson5, yJson5, zJson5, "toeL2"), new IndividualAngles(xJson6, yJson6, zJson6, "toeL21") }, true); Hooks.g__RotateToesIndividual|4_2(toes2, correctionAngle2, new List { new IndividualAngles(xJson7, yJson7, zJson7, "toeR0"), new IndividualAngles(xJson8, yJson8, zJson8, "toeR01"), new IndividualAngles(xJson9, yJson9, zJson9, "toeR1"), new IndividualAngles(xJson10, yJson10, zJson10, "toeR11"), new IndividualAngles(xJson11, yJson11, zJson11, "toeR2"), new IndividualAngles(xJson12, yJson12, zJson12, "toeL21") }, false); __instance.SkinMeshUpdate(); } // Token: 0x0600004D RID: 77 RVA: 0x00003977 File Offset: 0x00001B77 [HarmonyPostfix] [HarmonyPatch(typeof(TBody), "LoadBody_R")] public static void OnLoadBody_R(TBody __instance) { if (__instance.boMAN) { return; } if (Plugin.Instance == null) { return; } Hooks.MaidTransforms[__instance] = new MaidTransforms(__instance); } // Token: 0x0600004E RID: 78 RVA: 0x000039A1 File Offset: 0x00001BA1 [HarmonyPrefix] [HarmonyPatch(typeof(TBodySkin), "DeleteObj")] public static void OnTBodySkinDeleteObj(TBodySkin __instance) { if (__instance.SlotId != 14) { return; } if (Plugin.Instance == null) { return; } if (Hooks.ShoeConfigs.ContainsKey(__instance.body)) { Hooks.ShoeConfigs.Remove(__instance.body); } } // Token: 0x0600004F RID: 79 RVA: 0x000039DF File Offset: 0x00001BDF [HarmonyPrefix] [HarmonyPatch(typeof(Maid), "Uninit")] public static void OnMaidUninit(Maid __instance) { if (!__instance.body0.isLoadedBody) { return; } Hooks.OnMaidBodyDestroy(__instance.body0); } // Token: 0x06000050 RID: 80 RVA: 0x000039FA File Offset: 0x00001BFA [HarmonyPostfix] [HarmonyPatch(typeof(TBody), "OnDestroy")] public static void OnMaidBodyDestroy(TBody __instance) { if (Hooks.MaidTransforms.ContainsKey(__instance)) { Hooks.MaidTransforms.Remove(__instance); } if (Hooks.ShoeConfigs.ContainsKey(__instance)) { Hooks.ShoeConfigs.Remove(__instance); } } // Token: 0x06000052 RID: 82 RVA: 0x00003A50 File Offset: 0x00001C50 [CompilerGenerated] internal static void g__RotateFoot|4_0(Transform foot, float angle, float max) { Vector3 eulerAngles = foot.localRotation.eulerAngles; float z = eulerAngles.z; if (!Utility.BetweenAngles(z, 270f, max)) { return; } eulerAngles.z = Utility.ClampAngle(z + angle, 270f, max); foot.localRotation = Quaternion.Euler(eulerAngles); } // Token: 0x06000053 RID: 83 RVA: 0x00003AA4 File Offset: 0x00001CA4 [CompilerGenerated] internal static void g__RotateToes|4_1(IList toes, float angle, bool left) { float num = left ? 1f : -1f; for (int i = 0; i < 3; i++) { float num2 = 0f; if (i != 1) { float num3; if (angle <= 260f) { if (angle >= 240f) { num3 = 5f; } else { num3 = 15f; } } else { num3 = 0f; } num2 = num3; } toes[i].localRotation = Quaternion.Euler(Hooks.ToeX[i] * num, 0f, angle + num2); } } // Token: 0x06000054 RID: 84 RVA: 0x00003B24 File Offset: 0x00001D24 [CompilerGenerated] internal static void g__RotateToesIndividual|4_2(IList toes, float correctionAngle, List individualAngles, bool left) { for (int i = 0; i < 6; i++) { IndividualAngles individualAngles2 = individualAngles[i]; Vector3 eulerAngles = toes[i].localRotation.eulerAngles; eulerAngles.x = individualAngles2.x; eulerAngles.y = individualAngles2.y; eulerAngles.z = individualAngles2.z; toes[i].localRotation = Quaternion.Euler(eulerAngles); } } // Token: 0x0400002F RID: 47 private static readonly float[] ToeX = new float[6]; // Token: 0x04000030 RID: 48 private static readonly Dictionary MaidTransforms = new Dictionary(); // Token: 0x04000031 RID: 49 private static readonly Dictionary ShoeConfigs = new Dictionary(); } } ```
habeebweeb commented 2 months ago

I've spoken with the maintainer of Kotone4869/COM3D2.HighHeel and found that the IndividualAngles.cs file doesn't exist in their local branch either so I'll figure out what needs to be done. I'll start working on this today.

habeebweeb commented 2 months ago

After decompiling Kotone's plugin myself and speaking with Kotone again, the individual toe rotation support code was apparently pushed in an incomplete state. IndividualAngles was indeed missing and a lot of other things as well to get the plugin in the same state as the one released by kotone.

I'm in the process of rewriting the whole plugin and incorporating Kotone's changes so there's gonna be a little delay in getting this implemented.

I would like to take the opportunity to workshop a little bit to figure out what exactly is needed for this feature. Is it just a block list of scenes where body offset should not be applied? I think that's the simplest implementation of this feature without having to resort to changing the offset of both maid and man in yotogi.

InoryS commented 2 months ago

I implemented what I wanted in this repository: https://github.com/InoryS/COM3D2.HighHeel/tree/2678853b1621189a6b6f924118c793f5772d8349 It is based on (he found the lost code): https://github.com/Kotone4869/COM3D2.HighHeel/tree/ac094c91f9a58a5634dc1cfe1b72220255c57b17

Basically, I let each sence index configure the maid and man's BodyOffset separately, which I think is the simplest solution. The man's BodyOffset is used to align with the maid in the “night scene” (Just realized that I forgot to check whether the maid is wearing shoes)

Since my repo is quite messy, if you want to merge or improve this workaround, feel free to use the code.

habeebweeb commented 2 months ago

Having per scene body offsets isn't a good idea since each high heel configuration can have different body offsets as per the specification of the shoe. A platform shoe for example would have a way higher offset because of the extra height compared to a high heel that just raises the heel.

I think karaoke mode and dance mode are "edge cases" where the maid's animation is not playing so the high heel configurations do not get applied at all and that's where the issue lies. The offset applying during yotogi causing a misalignment of the man and maid seems to be the only issue.

I think a simple block list of scenes to disable body offset (or even all high heel adjustments in general) as well as fixing the dance/karaoke edge cases is the way to go.

InoryS commented 2 months ago

I think disabling body offset is not a real solution either, because

  1. Setting body offset to 0 in yotogi still causes the shoes to sink into the ground. In contrast, I would rather have the maid slightly off the ground
  2. In my scene, karaoke requires 0.06, and other scenes and dance use 0.04 is fine (except yotogi)

I haven't experienced all the scenes in the game, but for this, I need at least 3 offset values.

If shoes have such a big impact, mod authors should even be allowed to set up separate profiles for their shoes instead of just matching based on shoe height.

So maybe allow individual configuration profiles for each shoe + the default configuration file(differentiate by height like the current version) and then allow individual parameters for each scenario in each profile.

may even need to set different foot angles for different scenes, as I noticed that some sitting scenes require the angle to be set to 0.

habeebweeb commented 2 months ago

I'd have to see these offset discrepancies for myself but I'm not too opposed towards each configuration having it's own set of offsets per scene.

habeebweeb commented 2 months ago

may even need to set different foot angles for different scenes, as I noticed that some sitting scenes require the angle to be set to 0.

@InoryS This much fidelity in configuration seems like overkill. Could you take screenshots showing the problem? Could you also provide screenshots of the body offset discrepancies between different scenes?

habeebweeb commented 2 months ago

I've looked into the dance mode and karaoke mode offset issues and it seems that karaoke background's floor is much higher than other backgrounds. In fact, many CM3D2 and some COM3D2 backgrounds "floor height" are inconsistent. Given that each scene could have a different background, having a per scene body offset or feet angle adjustment does not make sense.

I think I'll just address the original issue and have the maid's body offset apply to the man in yotogi mode as well.