A stable modding interface between Techblox and mods https://mod.exmods.org/
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

218 строки
9.4KB

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