using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; using HarmonyLib; using DataLoader; using RobocraftX.Common; using RobocraftX.Rendering; using RobocraftX.Schedulers; using Svelto.ECS; using Svelto.ECS.Experimental; using Svelto.Tasks; using Svelto.Tasks.ExtraLean; using UnityEngine; using UnityEngine.AddressableAssets; using Material = UnityEngine.Material; using GamecraftModdingAPI.Utility; using ServiceLayer; namespace GamecraftModdingAPI.Blocks { /// /// Experimental support for adding custom blocks to the game. /// public class CustomBlock : Block { private static ushort nextID = 500; /// /// Key: Prefab path /// private static readonly Dictionary CustomBlocks = new Dictionary(); //private static readonly CustomBlockEngine Engine = new CustomBlockEngine(); private static readonly List<(ushort id, Action action)> BlockChangeActions = new List<(ushort, Action)>(); private static bool _canRegister = true; /// /// Register a custom block type. Call it as soon as possible (in OnApplicationStart()).
/// You need a Unity project with Addressables and Havok installed and need a prefab added as an addressable asset. /// Build the addressables and the project and copy the catalog.json from StreamingAssets, you'll need to reference this file. /// Also copy the asset files from the subfolder to the same path in the game. ///
/// The custom block type public static void RegisterCustomBlock() where T : CustomBlock { if (!_canRegister) throw new InvalidOperationException( "It's too late to register custom blocks. Register it before the game starts loading."); var type = typeof(T); var attr = type.GetCustomAttribute(); if (attr == null) throw new ArgumentException("The custom block type is missing the CustomBlock annotation"); string typeName = type.FullName ?? throw new ArgumentException("The given block type doesn't have a concrete full name."); if (!File.Exists(attr.Catalog)) throw new FileNotFoundException("The specified catalog cannot be found for " + typeName); CustomBlocks.Add(attr.AssetPath, type); Logging.MetaDebugLog("Registered custom block type " + typeName); } /// /// A low-level method for changing any property of an existing block. Use with caution. /// /// The block ID /// An action that modifies a property of the block public static void ChangeExistingBlock(ushort id, Action modifier) { BlockChangeActions.Add((id, modifier)); } public CustomBlock(EGID id) : base(id) { /*if (id.groupID != Group) throw new BlockTypeException("The block is not a custom block! It has a group of " + id.groupID);*/ } public CustomBlock(uint id) : base(id) { } //public static ExclusiveGroup Group { get; } = new ExclusiveGroup("Custom block"); //[HarmonyPatch] - TODO public static class MaterialCopyPatch { private static Material[] materials; public static void Prefix(List prefabData, IList prefabs) { for (var index = 0; index < prefabs.Count; index++) { if (prefabData[index].prefabName == "ConsoleBlock") materials = prefabs[index].GetComponentsInChildren()[0].sharedMaterials; } for (var index = 0; index < prefabs.Count; index++) { if (CustomBlocks.ContainsKey(prefabData[index].prefabName)) //This is a custom block prefabs[index].GetComponentsInChildren()[0].sharedMaterials = materials; } } public static MethodBase TargetMethod() { //General block registration return AccessTools.Method("RobocraftX.Rendering.ECSGPUIResourceManager:InitPreRegisteredPrefabs"); } } [HarmonyPatch] public static class CubeRegistrationPatch { public static void Prefix(IDataDB dataDB) { //var abd = dataDB.GetValue((int) BlockIDs.Cube); foreach (var (key, type) in CustomBlocks) { var attr = type.GetCustomAttribute(); var cld = new CubeListData { //"Assets/Prefabs/Cube.prefab" - "CTR_CommandBlock" - "strConsoleBlock" cubeType = attr.Type, //cubeCategory = (CubeCategory) 1000, cubeCategory = attr.Category, inventoryCategory = attr.InventoryCategory, ID = nextID++, Path = attr.AssetPath, //Index out of range exception: Asset failed to load (wrong path) SpriteName = attr.SpriteName, CubeNameKey = attr.NameKey, CubeDescriptionKey = attr.DescKey, SelectableFaces = new[] {0, 1, 2, 3, 4, 5}, GridScale = new[] {5, 5, 5}, DefaultMaterialID = 0, //TODO: Material API scalingPermission = attr.ScalingPermission, SortIndex = attr.SortIndex, DefaultColour = attr.DefaultColor.Index, Volume = attr.Volume, EdgeConnectingFaces = new[] {0, 1, 2, 3, 4, 5}, PointDataVolumeMultiplier = 1f }; dataDB.GetValues().Add(cld.ID.ToString(), cld); //The registration needs to happen after the ID has been set dataDB.GetFasterValues().Add(cld.ID, cld); //So can't use the builtin method to create a CubeListData //Engine.RegisterBlock((ushort) cld.ID, key); - TODO } foreach (var (id, action) in BlockChangeActions) action(dataDB.GetValue(id)); /*foreach (var (key, value) in dataDB.GetValues()) { var data = (CubeListData) value; Console.WriteLine($"ID: {key} - Name: {data.CubeNameKey}: {LocalizationService.Localize(data.CubeNameKey)}"); }*/ _canRegister = false; } public static MethodBase TargetMethod() { return AccessTools.Method("RobocraftX.CR.MainGame.MainGameCompositionRoot:Init"); } } /*[HarmonyPatch] - The block has no collision even in simulation if using a custom category private static class FactorySetupPatch { public static void Prefix(BlockEntityFactory __instance) { var builders = (Dictionary) AccessTools.Field(__instance.GetType(), "_blockBuilders").GetValue(__instance); builders.Add((CubeCategory) 1000, new BlockBuilder(Group)); } public static MethodBase TargetMethod() { return AccessTools.Method(typeof(BlockEntityFactory), "ParseDataDB"); } }*/ private static IEnumerator Prepare() { //Should be pretty quick foreach (var type in CustomBlocks.Values) { var attr = type.GetCustomAttribute(); Logging.Log("Loading custom block catalog " + attr.Catalog); var res = Addressables.LoadContentCatalogAsync(attr.Catalog); while (!res.IsDone) yield return Yield.It; Logging.Log("Loaded custom block catalog: " + res.Result.LocatorId); Addressables.AddResourceLocator(res.Result); } } internal new static void Init() { Prepare().RunOn(ExtraLean.UIScheduler); //GameEngineManager.AddGameEngine(Engine); - TODO: Fix serialization and implement block ID update } /*internal static void OnBlockFactoryObtained(BlockEntityFactory factory) { var builders = (Dictionary) AccessTools.Field(factory.GetType(), "_blockBuilders").GetValue(factory); builders.Add((CubeCategory) 1000, new BlockBuilder(Group)); }*/ internal struct DataStruct : IEntityComponent { public ECSString Name; public ushort ID; } } }