A stable modding interface between Techblox and mods https://mod.exmods.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

192 lines
8.1KB

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Reflection;
  6. using HarmonyLib;
  7. using DataLoader;
  8. using RobocraftX.Rendering;
  9. using RobocraftX.Schedulers;
  10. using Svelto.ECS;
  11. using Svelto.ECS.Experimental;
  12. using Svelto.Tasks;
  13. using Svelto.Tasks.ExtraLean;
  14. using UnityEngine;
  15. using UnityEngine.AddressableAssets;
  16. using Material = UnityEngine.Material;
  17. using GamecraftModdingAPI.Utility;
  18. namespace GamecraftModdingAPI.Blocks
  19. {
  20. /// <summary>
  21. /// Experimental support for adding custom blocks to the game.
  22. /// </summary>
  23. public class CustomBlock : Block
  24. {
  25. private static ushort nextID = 500;
  26. /// <summary>
  27. /// Key: Prefab path
  28. /// </summary>
  29. private static readonly Dictionary<string, Type> CustomBlocks = new Dictionary<string, Type>();
  30. private static readonly CustomBlockEngine Engine = new CustomBlockEngine();
  31. private static bool _canRegister = true;
  32. /// <summary>
  33. /// Register a custom block type. Call it as soon as possible (in OnApplicationStart()).<br />
  34. /// You need a Unity project with Addressables and Havok installed and need a prefab added as an addressable asset.
  35. /// Build the addressables and the project and copy the catalog.json from StreamingAssets, you'll need to reference this file.
  36. /// Also copy the asset files from the subfolder to the same path in the game.
  37. /// </summary>
  38. /// <typeparam name="T">The custom block type</typeparam>
  39. public static void RegisterCustomBlock<T>() where T : CustomBlock
  40. {
  41. if (!_canRegister)
  42. throw new InvalidOperationException(
  43. "It's too late to register custom blocks. Register it before the game starts loading.");
  44. var type = typeof(T);
  45. var attr = type.GetCustomAttribute<CustomBlockAttribute>();
  46. if (attr == null)
  47. throw new ArgumentException("The custom block type is missing the CustomBlock annotation");
  48. string typeName = type.FullName ??
  49. throw new ArgumentException("The given block type doesn't have a concrete full name.");
  50. if (!File.Exists(attr.Catalog))
  51. throw new FileNotFoundException("The specified catalog cannot be found for " + typeName);
  52. CustomBlocks.Add(attr.AssetPath, type);
  53. Logging.MetaDebugLog("Registered custom block type " + typeName);
  54. }
  55. public CustomBlock(EGID id) : base(id)
  56. {
  57. /*if (id.groupID != Group)
  58. throw new BlockTypeException("The block is not a custom block! It has a group of " + id.groupID);*/
  59. }
  60. public CustomBlock(uint id) : base(id)
  61. {
  62. }
  63. //public static ExclusiveGroup Group { get; } = new ExclusiveGroup("Custom block");
  64. [HarmonyPatch]
  65. public static class MaterialCopyPatch
  66. {
  67. private static Material[] materials;
  68. public static void Prefix(List<PrefabData> prefabData, IList<GameObject> prefabs)
  69. {
  70. for (var index = 0; index < prefabs.Count; index++)
  71. {
  72. if (prefabData[index].prefabName == "ConsoleBlock")
  73. materials = prefabs[index].GetComponentsInChildren<MeshRenderer>()[0].sharedMaterials;
  74. }
  75. for (var index = 0; index < prefabs.Count; index++)
  76. {
  77. if (CustomBlocks.ContainsKey(prefabData[index].prefabName)) //This is a custom block
  78. prefabs[index].GetComponentsInChildren<MeshRenderer>()[0].sharedMaterials = materials;
  79. }
  80. }
  81. public static MethodBase TargetMethod()
  82. { //General block registration
  83. return AccessTools.Method("RobocraftX.Rendering.ECSGPUIResourceManager:InitPreRegisteredPrefabs");
  84. }
  85. }
  86. [HarmonyPatch]
  87. public static class CubeRegistrationPatch
  88. {
  89. public static void Prefix(IDataDB dataDB)
  90. {
  91. //var abd = dataDB.GetValue<CubeListData>((int) BlockIDs.AluminiumCube);
  92. foreach (var (key, type) in CustomBlocks)
  93. {
  94. var attr = type.GetCustomAttribute<CustomBlockAttribute>();
  95. var cld = new CubeListData
  96. { //"Assets/Prefabs/Cube.prefab" - "CTR_CommandBlock" - "strConsoleBlock"
  97. cubeType = attr.Type,
  98. //cubeCategory = (CubeCategory) 1000,
  99. cubeCategory = attr.Category,
  100. inventoryCategory = attr.InventoryCategory,
  101. ID = nextID++,
  102. Path = attr.AssetPath, //Index out of range exception: Asset failed to load (wrong path)
  103. SpriteName = attr.SpriteName,
  104. CubeNameKey = attr.NameKey,
  105. CubeDescriptionKey = attr.DescKey,
  106. SelectableFaces = new[] {0, 1, 2, 3, 4, 5},
  107. GridScale = new[] {5, 5, 5},
  108. Mass = attr.Mass,
  109. Material = attr.Material,
  110. scalingPermission = attr.ScalingPermission,
  111. SortIndex = attr.SortIndex,
  112. DefaultColour = attr.DefaultColor.Index,
  113. Volume = attr.Volume,
  114. EdgeConnectingFaces = new[] {0, 1, 2, 3, 4, 5},
  115. PointDataVolumeMultiplier = 1f
  116. };
  117. dataDB.GetValues<CubeListData>().Add(cld.ID.ToString(), cld); //The registration needs to happen after the ID has been set
  118. dataDB.GetFasterValues<CubeListData>().Add(cld.ID, cld); //So can't use the builtin method to create a CubeListData
  119. Engine.RegisterBlock((ushort) cld.ID, key);
  120. }
  121. _canRegister = false;
  122. }
  123. public static MethodBase TargetMethod()
  124. {
  125. return AccessTools.Method("RobocraftX.CR.MainGame.MainGameCompositionRoot:Init");
  126. }
  127. }
  128. /*[HarmonyPatch] - The block has no collision even in simulation if using a custom category
  129. private static class FactorySetupPatch
  130. {
  131. public static void Prefix(BlockEntityFactory __instance)
  132. {
  133. var builders = (Dictionary<CubeCategory, IBlockBuilder>)
  134. AccessTools.Field(__instance.GetType(), "_blockBuilders").GetValue(__instance);
  135. builders.Add((CubeCategory) 1000, new BlockBuilder<StandardBlockEntityDescriptor>(Group));
  136. }
  137. public static MethodBase TargetMethod()
  138. {
  139. return AccessTools.Method(typeof(BlockEntityFactory), "ParseDataDB");
  140. }
  141. }*/
  142. private static IEnumerator Prepare()
  143. { //Should be pretty quick
  144. foreach (var type in CustomBlocks.Values)
  145. {
  146. var attr = type.GetCustomAttribute<CustomBlockAttribute>();
  147. Logging.Log("Loading custom block catalog " + attr.Catalog);
  148. var res = Addressables.LoadContentCatalogAsync(attr.Catalog);
  149. while (!res.IsDone) yield return Yield.It;
  150. Logging.Log("Loaded custom block catalog: " + res.Result.LocatorId);
  151. Addressables.AddResourceLocator(res.Result);
  152. }
  153. }
  154. internal new static void Init()
  155. {
  156. Prepare().RunOn(ExtraLean.UIScheduler);
  157. GameEngineManager.AddGameEngine(Engine);
  158. }
  159. /*internal static void OnBlockFactoryObtained(BlockEntityFactory factory)
  160. {
  161. var builders = (Dictionary<CubeCategory, IBlockBuilder>)
  162. AccessTools.Field(factory.GetType(), "_blockBuilders").GetValue(factory);
  163. builders.Add((CubeCategory) 1000, new BlockBuilder<StandardBlockEntityDescriptor>(Group));
  164. }*/
  165. internal struct DataStruct : IEntityComponent
  166. {
  167. public ECSString Name;
  168. public ushort ID;
  169. }
  170. }
  171. }