Skip to content

Commit

Permalink
Merge pull request #417 from OrianaVenture/Venture-MeshMocking
Browse files Browse the repository at this point in the history
Added feature to help resolve mocks based off same type search.
  • Loading branch information
MSchmoecker authored Jan 1, 2024
2 parents 1c6f67b + f1963de commit af2f28b
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 38 deletions.
34 changes: 34 additions & 0 deletions JotunnLib/Extensions/GameObjectExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using UnityEngine;
using UnityEngine.UI;
using Jotunn.Extensions;
using System.Collections.Generic;

namespace Jotunn
{
Expand Down Expand Up @@ -122,6 +123,7 @@ public static bool HasAnyComponent(this GameObject gameObject, params Type[] com
return true;
}
}

return false;
}

Expand All @@ -140,6 +142,7 @@ public static bool HasAnyComponent(this GameObject gameObject, params string[] c
return true;
}
}

return false;
}

Expand All @@ -158,6 +161,7 @@ public static bool HasAllComponents(this GameObject gameObject, params string[]
return false;
}
}

return true;
}

Expand All @@ -176,6 +180,7 @@ public static bool HasAllComponents(this GameObject gameObject, params Type[] co
return false;
}
}

return true;
}

Expand All @@ -200,6 +205,7 @@ params Type[] components
return true;
}
}

return false;
}

Expand All @@ -218,6 +224,34 @@ public static Transform FindDeepChild(
{
return gameObject.transform.FindDeepChild(childName, searchType);
}

/// <summary>
/// Extension method to find nested children by an ordered list of names using either
/// a breadth-first or depth-first search. Default is breadth-first.
/// </summary>
/// <param name="gameObject"></param>
/// <param name="childNames">Names in order of the child object to search for.</param>
/// <param name="searchType">Whether to preform a breadth first or depth first search. Default is breadth first.</param>
public static Transform FindDeepChild(
this GameObject gameObject,
IEnumerable<string> childNames,
global::Utils.IterativeSearchType searchType = global::Utils.IterativeSearchType.BreadthFirst
)
{
var child = gameObject.transform;

foreach (string childName in childNames)
{
child = child.FindDeepChild(childName, searchType);

if (!child)
{
return null;
}
}

return child;
}
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion JotunnLib/Managers/CreatureManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ private void FixReferences(ZNetScene self)
}
catch (MockResolveException ex)
{
Logger.LogWarning(customCreature?.SourceMod, $"Skipping creature {customCreature}: could not resolve mock {ex.MockType.Name} {ex.FailedMockName}");
Logger.LogWarning(customCreature?.SourceMod, $"Skipping creature {customCreature}: {ex.Message}");
toDelete.Add(customCreature);
}
catch (Exception ex)
Expand Down
8 changes: 4 additions & 4 deletions JotunnLib/Managers/ItemManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ private void RegisterCustomItems(ObjectDB objectDB)
}
catch (MockResolveException ex)
{
Logger.LogWarning(customItem?.SourceMod, $"Skipping item {customItem}: could not resolve mock {ex.MockType.Name} {ex.FailedMockName}");
Logger.LogWarning(customItem?.SourceMod, $"Skipping item {customItem}: {ex.Message}");
toDelete.Add(customItem);
}
catch (Exception ex)
Expand Down Expand Up @@ -509,7 +509,7 @@ private void RegisterCustomRecipes(ObjectDB objectDB)
}
catch (MockResolveException ex)
{
Logger.LogWarning(customRecipe?.SourceMod, $"Skipping recipe {customRecipe}: could not resolve mock {ex.MockType.Name} {ex.FailedMockName}");
Logger.LogWarning(customRecipe?.SourceMod, $"Skipping recipe {customRecipe}: {ex.Message}");
toDelete.Add(customRecipe);
}
catch (Exception ex)
Expand Down Expand Up @@ -556,7 +556,7 @@ private void RegisterCustomStatusEffects(ObjectDB objectDB)
}
catch (MockResolveException ex)
{
Logger.LogWarning(customStatusEffect?.SourceMod, $"Skipping status effect {customStatusEffect}: could not resolve mock {ex.MockType.Name} {ex.FailedMockName}");
Logger.LogWarning(customStatusEffect?.SourceMod, $"Skipping status effect {customStatusEffect}: {ex.Message}");
toDelete.Add(customStatusEffect);
}
catch (Exception ex)
Expand Down Expand Up @@ -666,7 +666,7 @@ private void RegisterCustomItemConversions()
}
catch (MockResolveException ex)
{
Logger.LogWarning(conversion?.SourceMod, $"Skipping item conversion {conversion}: could not resolve mock {ex.MockType.Name} {ex.FailedMockName}");
Logger.LogWarning(conversion?.SourceMod, $"Skipping item conversion {conversion}: {ex.Message}");
toDelete.Add(conversion);
}
catch (Exception ex)
Expand Down
152 changes: 129 additions & 23 deletions JotunnLib/Managers/MockSystem/MockManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using Jotunn.Extensions;
Expand Down Expand Up @@ -42,6 +43,11 @@ private MockManager()
/// </summary>
public const string JVLMockPrefix = "JVLmock_";

/// <summary>
/// String used by the Mock System to recognize start of a relative path of a replacement for a Mock gameObject.
/// </summary>
public const string JVLMockSeparator = "__";

/// <summary>
/// Internal container for mocked prefabs
/// </summary>
Expand Down Expand Up @@ -136,36 +142,56 @@ public static T GetRealPrefabFromMock<T>(Object unityObject) where T : Object
/// <returns>the real prefab</returns>
public static Object GetRealPrefabFromMock(Object unityObject, Type mockObjectType)
{
if (!unityObject)
if (!unityObject || !IsMockName(unityObject.name, out string assetName, out List<string> childNames))
{
return null;
}

var unityObjectName = unityObject.name;
if (IsMockName(unityObjectName, out unityObjectName))
if (childNames.Count == 0 && TryGetAsset(mockObjectType, assetName, out Object asset))
{
// Cut off the suffix in the name to correctly query the original material
if (unityObject is Material)
{
unityObjectName = unityObjectName.RemoveSuffix(" (Instance)");
}
return asset;
}

var prefab = PrefabManager.Cache.GetPrefab<GameObject>(assetName);

Object ret = PrefabManager.Cache.GetPrefab(mockObjectType, unityObjectName);
if (!prefab)
{
throw new MockResolveException($"Object with name '{assetName}' was not found.", assetName, mockObjectType);
}

if (!ret)
if (childNames.Count > 0)
{
var child = prefab.FindDeepChild(childNames);

if (!child || child.name != childNames.Last())
{
throw new MockResolveException($"Mock {mockObjectType.Name} {unityObjectName} could not be resolved", unityObjectName, mockObjectType);
throw new MockResolveException($"Child '{childNames.Last()}' not found with the specified path.", assetName, childNames, mockObjectType);
}

return ret;
prefab = child.gameObject;
}

if (unityObject is Material material)
if (TryFindAssetInSelfOrChildComponents(prefab, mockObjectType, out asset))
{
TryFixMaterial(material);
return asset;
}

return null;
if (childNames.Count > 0)
{
var usedPath = prefab.transform.GetPath().TrimStart('/');
throw new MockResolveException($"{mockObjectType.Name} not found at child '{usedPath}'.", assetName, childNames, mockObjectType);
}
else
{
throw new MockResolveException($"{mockObjectType.Name} not found at prefab '{assetName}'.", assetName, mockObjectType);
}
}

private static bool TryGetAsset(Type mockObjectType, string assetName, out Object asset)
{
assetName = GetCleanedName(mockObjectType, assetName);
asset = PrefabManager.Cache.GetPrefab(mockObjectType, assetName);
return (bool)asset;
}

internal static void FixReferences(object objectToFix, int depth)
Expand All @@ -186,26 +212,45 @@ internal static void FixReferences(object objectToFix, int depth)
}
}

private static bool IsMockName(string name, out string cleanedName)
private static bool IsMockName(string name, out string assetName, out List<string> childNames)
{
if (name.StartsWith(JVLMockPrefix, StringComparison.Ordinal))
{
cleanedName = name.Substring(JVLMockPrefix.Length);
var splitNames = name.Split(new[] { JVLMockSeparator }, StringSplitOptions.RemoveEmptyEntries);
assetName = splitNames[0].Substring(JVLMockPrefix.Length);
childNames = splitNames.Skip(1).ToList();
return true;
}

#pragma warning disable CS0618
if (name.StartsWith(MockPrefix, StringComparison.Ordinal))
{
cleanedName = name.Substring(MockPrefix.Length);
assetName = name.Substring(MockPrefix.Length);
childNames = new List<string>();
return true;
}
#pragma warning restore CS0618

cleanedName = name;
assetName = name;
childNames = new List<string>();
return false;
}

private static string GetCleanedName(Type objectType, string name)
{
// Cut off the suffix in the name to correctly query the original
if (objectType == typeof(Material))
{
return name.RemoveSuffix(" (Instance)");
}
else if (objectType == typeof(Mesh))
{
return name.RemoveSuffix(" Instance");
}

return name;
}

private static void FixMemberReferences(MemberBase member, object objectToFix, int depth)
{
// Special treatment for DropTable, its a List of struct DropData
Expand All @@ -225,6 +270,10 @@ private static void FixMemberReferences(MemberBase member, object objectToFix, i
{
member.SetValue(objectToFix, realPrefab);
}
else if (target is Material material)
{
TryFixMaterial(material);
}
}
else if (member.IsEnumeratedClass && member.IsEnumerableOfUnityObjects)
{
Expand Down Expand Up @@ -252,6 +301,11 @@ private static void FixMemberReferences(MemberBase member, object objectToFix, i
var realPrefab = GetRealPrefabFromMock(unityObject, member.EnumeratedType);
list.Add(realPrefab ? realPrefab : unityObject);
hasAnyMockResolved = hasAnyMockResolved || realPrefab;

if (!realPrefab && unityObject is Material material)
{
TryFixMaterial(material);
}
}

if (list.Count > 0 && hasAnyMockResolved)
Expand Down Expand Up @@ -318,10 +372,10 @@ private static void FixDropTable(MemberBase member, object objectToFix)
for (int i = 0; i < drops.Count; i++)
{
var drop = drops[i];
var realPrefab = GetRealPrefabFromMock(drop.m_item, typeof(GameObject));
var realPrefab = GetRealPrefabFromMock<GameObject>(drop.m_item);
if (realPrefab)
{
drop.m_item = (GameObject)realPrefab;
drop.m_item = realPrefab;
}

drops[i] = drop;
Expand Down Expand Up @@ -429,7 +483,7 @@ private static bool FixShader(Material material)
{
Shader usedShader = material.shader;

if (!usedShader || !IsMockName(usedShader.name, out string cleanedShaderName))
if (!usedShader || !IsMockName(usedShader.name, out string cleanedShaderName, out List<string> childNames))
{
return true;
}
Expand All @@ -452,5 +506,57 @@ private static bool FixShader(Material material)

return true;
}

private static bool TryFindAssetOfComponent(Component unityObject, Type objectType, out Object asset)
{
var type = unityObject.GetType();
ClassMember classMember = ClassMember.GetClassMember(type);

foreach (var member in classMember.Members)
{
if (member.MemberType == objectType && member.HasGetMethod)
{
asset = (Object)member.GetValue(unityObject);
if (asset != null)
{
return asset;
}
}
}

asset = null;
return false;
}

private static bool TryFindAssetInSelfOrChildComponents(GameObject unityObject, Type objectType, out Object asset)
{
if (unityObject == null)
{
asset = null;
return false;
}

foreach (var component in unityObject.GetComponents<Component>())
{
if (!(component is Transform))
{
if (TryFindAssetOfComponent(component, objectType, out asset))
{
return (bool)asset;
}
}
}

foreach (Transform tf in unityObject.transform)
{
if (TryFindAssetInSelfOrChildComponents(tf.gameObject, objectType, out asset))
{
return (bool)asset;
}
}

asset = null;
return false;
}
}
}
Loading

0 comments on commit af2f28b

Please sign in to comment.