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.

CustomBlock.cs 9.2KB

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