diff --git a/GamecraftModdingAPI/Block.cs b/GamecraftModdingAPI/Block.cs
index 524847d..4aa0a30 100644
--- a/GamecraftModdingAPI/Block.cs
+++ b/GamecraftModdingAPI/Block.cs
@@ -213,7 +213,7 @@ namespace GamecraftModdingAPI
throw new BlockTypeException("The block has the wrong group! The type is " + GetType() +
" while the group is " + id.groupID);
}
- else if (type != typeof(Block))
+ else if (type != typeof(Block) && !typeof(CustomBlock).IsAssignableFrom(type))
Logging.LogWarning($"Unknown block type! Add {type} to the dictionary.");
}
diff --git a/GamecraftModdingAPI/Blocks/CustomBlock.cs b/GamecraftModdingAPI/Blocks/CustomBlock.cs
index 2f33035..331552c 100644
--- a/GamecraftModdingAPI/Blocks/CustomBlock.cs
+++ b/GamecraftModdingAPI/Blocks/CustomBlock.cs
@@ -1,9 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Reflection;
using DataLoader;
+using GamecraftModdingAPI.App;
using GamecraftModdingAPI.Utility;
using GPUInstancer;
using HarmonyLib;
@@ -11,8 +13,8 @@ using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.Rendering;
using Svelto.DataStructures;
+using Svelto.ECS;
using Svelto.Tasks;
-using Unity.Entities;
using Unity.Entities.Conversion;
using Unity.Physics;
using UnityEngine;
@@ -24,264 +26,113 @@ using ScalingPermission = DataLoader.ScalingPermission;
namespace GamecraftModdingAPI.Blocks
{
- public class CustomBlock
+ public class CustomBlock : Block
{
private static ushort nextID = 500;
- public static void RegisterCustomBlock(string path)
+ ///
+ /// Key: Prefab path
+ ///
+ private static Dictionary _customBlocks = new Dictionary();
+
+ 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
{
- var prefabData = new List();
- //category ID:
- //0 - regular
- //1 - joint
- //2 - controller
- uint prefabId = PrefabsID.FetchNewPrefabID(PrefabsID.GenerateDBID(0, nextID++));
- prefabData.Add(new PrefabData()
- {
- prefabName = path,
- prefabId = prefabId
- });
- var loadTask = Addressables.LoadAssetAsync(path);
- AccessTools.Method("RobocraftX.Common.ECSGPUIResourceManager:RegisterPrefab")
- .Invoke(ECSGPUIResourceManager.Instance, new object[] {prefabId, loadTask.Result, 1});
+ 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);
}
- [HarmonyPatch]
- public static class Patch
+ public CustomBlock(EGID id) : base(id)
{
- /*public static IEnumerable Transpiler(IEnumerable instructions)
- {
- var list = new List(instructions);
- try
- {
- *int index = -1;
- CodeInstruction loadTask = null;
- for (var i = 0; i < list.Count - 1; i++)
- {
- if (list[i].opcode == OpCodes.Ldfld
- && ((string) list[i].operand).Contains("renderingWorld")
- && list[i + 1].opcode == OpCodes.Brfalse_S)
- index = i - 1; //It loads 'this' first
- if (list[i].opcode == OpCodes.Ldflda
- && ((string) list[i].operand).Contains("loadTask"))
- loadTask = new CodeInstruction(list[i]);
- }*
+ if (id.groupID != Group)
+ throw new BlockTypeException("The block is not a custom block! It has a group of " + id.groupID);
+ }
- var array = new[]
- {
- //Set Yield.It to be returned (current)
- new CodeInstruction(OpCodes.Ldarg_0), // this
- new CodeInstruction(OpCodes.Ldsfld,
- typeof(Yield).GetField("It", BindingFlags.Public | BindingFlags.Static)),
- new CodeInstruction(OpCodes.Stfld, "object RobocraftX.Common.ECSResourceManagerUtility/'d__0'::'<>2__current'"),
-
- //Set which yield return we're at (state)
- new CodeInstruction(OpCodes.Ldarg_0), // this
- new CodeInstruction(OpCodes.Ldc_I4_1),
- //new CodeInstruction(OpCodes.Call, ((Action)AddInfo).Method)
- };
- list.InsertRange(index, array);
- *
- IL_00ad: ldarg.0 // this
- IL_00ae: ldsfld class [Svelto.Tasks]Svelto.Tasks.Yield [Svelto.Tasks]Svelto.Tasks.Yield::It
- IL_00b3: stfld object RobocraftX.Common.ECSResourceManagerUtility/'d__0'::'<>2__current'
-
- IL_0072: ldarg.0 // this
- IL_0073: ldnull
- IL_0074: stfld object RobocraftX.Common.ECSResourceManagerUtility/'d__0'::'<>2__current'
- IL_0079: ldarg.0 // this
- IL_007a: ldc.i4.2
- IL_007b: stfld object RobocraftX.Common.ECSResourceManagerUtility/'d__0'::'<>1__state'
- IL_0080: ldc.i4.1
- IL_0081: ret
- *
- yield break;
- }
- catch (Exception e)
- {
- Logging.LogWarning("Failed to inject AddInfo method for the debug display!\n" + e);
- }
- }*/
+ public CustomBlock(uint id) : this(new EGID(id, Group))
+ {
+ }
+
+ public static ExclusiveGroup Group { get; } = new ExclusiveGroup("Custom block");
+ [HarmonyPatch]
+ public static class Patch
+ {
private static Material[] materials;
public static void Prefix(List prefabData, IList prefabs)
{
- /*foreach (var block in blocks.Values.Cast())
+ for (var index = 0; index < prefabs.Count; index++)
{
- Console.WriteLine("Block info: " + block);
- }*/
+ if (prefabData[index].prefabName == "ConsoleBlock")
+ materials = prefabs[index].GetComponentsInChildren()[0].sharedMaterials;
+ }
- /*var res = Addressables.LoadContentCatalogAsync("customCatalog.json");
- while (!res.IsDone) yield return Yield.It;*/
- foreach (var gameObject in prefabs)
+ for (var index = 0; index < prefabs.Count; index++)
{
- switch (gameObject.name)
- {
- case "Cube":
- gameObject.GetComponentsInChildren()[0].sharedMaterials = materials;
- break;
- case "CTR_CommandBlock":
- materials = gameObject.GetComponentsInChildren()[0].sharedMaterials;
- break;
- }
+ 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.Blocks.BlocksCompositionRoot:RegisterPartPrefabs");
return AccessTools.Method("RobocraftX.Rendering.ECSGPUIResourceManager:InitPreRegisteredPrefabs");
}
}
- /*[HarmonyPatch]
- public static class RendererPatch
- {
- private static Material[] materials;
- public static void Prefix(uint prefabID, GameObject gameObject)
- {
- Console.WriteLine("ID: " + prefabID + " - Name: " + gameObject.name);
- if (gameObject.name == "Cube")
- {
- //Console.WriteLine("Length: " + gameObject.GetComponentsInChildren().Length);
- if (materials != null)
- gameObject.GetComponentsInChildren()[0].sharedMaterials = materials;
- /*ECSGPUIResourceManager.Instance.RegisterGhostsPrefabsAtRuntime(
- new[] {new PrefabData {prefabId = prefabID, prefabName = "Assets/Prefabs/Cube.prefab"}},
- new List {gameObject});#1#
- GameObject go = Object.Instantiate(gameObject);
- go.SetActive(false);
- AccessTools.Method("RobocraftX.Rendering.ECSGPUIResourceManager:RegisterNewPrefabAtRuntime")
- .Invoke(ECSGPUIResourceManager.Instance,
- new object[] {(uint) ((int) prefabID * 2 + 1), gameObject, true});
- //ECSGPUIResourceManager.Instance.RegisterNewPrefabAtRuntime((uint) ((int)prefabID * 2 + 1), gameObject, true); //.isOcclusionCulling = false;
- UnityEngine.Object.Destroy(gameObject);
- GPUInstancerAPI.AddInstancesToPrefabPrototypeAtRuntime(ECSGPUIResourceManager.Instance.prefabManager,
- gameObject.GetComponent().prefabPrototype, new[] {gameObject});
- Console.WriteLine("Registered prefab to instancer");
-
- /*var register = AccessTools.Method("RobocraftX.Common.ECSPhysicResourceManager:RegisterPrefab",
- new[] {typeof(uint), typeof(GameObject), typeof(World), typeof(BlobAssetStore)});
- register.Invoke(ECSPhysicResourceManager.Instance,
- new object[] {prefabID, gameObject, MGPatch.data.Item1, MGPatch.data.Item2});#1#
- /*Console.WriteLine(
- "Entity: " + ECSPhysicResourceManager.Instance.GetUECSPhysicEntityPrefab(prefabID));
- Console.WriteLine("Prefab ID: " + PrefabsID.DBIDMAP[500]);
- PhysicsCollider componentData = MGPatch.data.Item1.EntityManager.GetComponentData(ECSPhysicResourceManager.Instance.GetUECSPhysicEntityPrefab(prefabID));
- Console.WriteLine("Collider valid: " + componentData.IsValid);
- unsafe
- {
- Console.WriteLine("Collider type: " + componentData.ColliderPtr->Type);
- CollisionFilter filter = componentData.Value.Value.Filter;
- Console.WriteLine("Filter not empty: " + !filter.IsEmpty);
- }#1#
- //MGPatch.data.Item1.EntityManager.GetComponentData<>()
- gameObject.AddComponent();
- gameObject.AddComponent();
- Console.WriteLine("Registered prefab to physics");
- ECSGPUIResourceManager.Instance.RegisterGhostsPrefabsAtRuntime();
- }
- else if (gameObject.name == "CTR_CommandBlock")
- materials = gameObject.GetComponentsInChildren()[0].sharedMaterials;
- }
-
- public static MethodBase TargetMethod()
- {RobocraftX.Common.ECSGPUIResourceManager.RegisterPrefab
- return AccessTools.Method("RobocraftX.Common.ECSGPUIResourceManager:RegisterPrefab",
- new[] {typeof(uint), typeof(GameObject)});
- }
- }*/
-
[HarmonyPatch]
- public static class RMPatch
+ public static class CubeRegistrationPatch
{
- public static void Prefix(World physicsWorld,
- GameObjectConversionSystem getExistingSystem,
- FasterList gos,
- List prefabData)
+ public static void Prefix(IDataDB dataDB)
{
- Console.WriteLine("First game object data:\n" + gos[0].GetComponents()
- .Select(c => c + " - " + c.name + " " + c.GetType())
- .Aggregate((a, b) => a + "\n" + b));
- for (var index = 0; index < prefabData.Count; index++)
+ //var abd = dataDB.GetValue((int) BlockIDs.AluminiumCube);
+ foreach (var (key, type) in _customBlocks)
{
- var data = prefabData[index];
- if (!data.prefabName.EndsWith("Cube.prefab")) continue;
- //getExistingSystem.DeclareLinkedEntityGroup(gos[index]);
- /*Entity entity = GameObjectConversionUtility.ConvertGameObjectHierarchy(gos[index],
- GameObjectConversionSettings.FromWorld(physicsWorld, MGPatch.data.Item2));*/
- Console.WriteLine("Transform: " + gos[index].transform.childCount);
- Entity primaryEntity = getExistingSystem.GetPrimaryEntity(gos[index]);
- MultiListEnumerator entities = getExistingSystem.GetEntities(gos[index]);
- Console.WriteLine("ID: " + data.prefabId + " - Name: " + data.prefabName);
- Console.WriteLine("Primary entity: " + primaryEntity);
- EntityManager entityManager = physicsWorld.EntityManager;
- Console.WriteLine("Has collider: " + entityManager.HasComponent(primaryEntity));
- while (entities.MoveNext())
- {
- Entity current = entities.Current;
- Console.WriteLine("Entity " + current + " has collider: " +
- entityManager.HasComponent(current));
- }
-
- Console.WriteLine("Adding GPUI prefab");
- ((GPUInstancerPrefabManager) GPUInstancerManager.activeManagerList.Single(gim =>
- gim is GPUInstancerPrefabManager))
- .AddRuntimeRegisteredPrefab(gos[index].GetComponent());
- Console.WriteLine("Added GPUI prefab");
+ var attr = type.GetCustomAttribute();
+ var cld = new CubeListData
+ { //"Assets/Prefabs/Cube.prefab" - "CTR_CommandBlock" - "strConsoleBlock"
+ cubeType = attr.Type,
+ 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},
+ Mass = attr.Mass,
+ Material = attr.Material,
+ 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
}
- }
- public static MethodBase TargetMethod()
- {
- return AccessTools.Method("RobocraftX.Blocks.ECSResourceManagerUtility:RelinkEntities",
- new[]
- {
- typeof(World),
- typeof(GameObjectConversionSystem),
- typeof(FasterList),
- typeof(List)
- });
- }
- }
-
- [HarmonyPatch]
- public static class MGPatch
- {
- internal static (World, BlobAssetStore) data;
- public static void Prefix(World physicsWorld, BlobAssetStore blobStore, IDataDB dataDB)
- {
- data = (physicsWorld, blobStore);
- //RobocraftX.CR.MachineEditing.UpdateSelectedGhostBlockEngine.UpdateGhostBlock
- //var cld = (CubeListData) ((DataImplementor) dataDB).CreateDataObject("500", typeof(CubeListData), null);
- var abd = dataDB.GetValue((int) BlockIDs.AluminiumCube);
- var cld = new CubeListData
- {
- cubeType = CubeType.Block,
- cubeCategory = CubeCategory.General,
- inventoryCategory = InventoryCategory.Shapes,
- ID = 500,
- Path = "Assets/Prefabs/Cube.prefab", //Index out of range exception: Asset failed to load (wrong path)
- SpriteName = "CTR_CommandBlock",
- CubeNameKey = "strConsoleBlock",
- SelectableFaces = new[] {0, 1, 2, 3, 4, 5},
- GridScale = new[] {1, 1, 1},
- Mass = 1,
- Material = abd.Material,
- scalingPermission = ScalingPermission.NonUniform,
- SortIndex = 12,
- DefaultColour = (byte) BlockColors.Lime,
- Volume = 1f,
- timeRunningCollision = TimeRunningCollision.Enabled,
- IsIsolator = false,
- EdgeConnectingFaces = new[] {0, 1, 2, 3, 4, 5},
- PointDataVolumeMultiplier = 1f
- };
- Console.WriteLine("Aluminium block data:\n" + abd);
- Console.WriteLine("Material: " + abd.Material);
- dataDB.GetValues().Add("500", cld); //The registration needs to happen after the ID has been set
- dataDB.GetFasterValues().Add(500, cld);
- //RobocraftX.ExplosionFragments.Engines.PlayFragmentExplodeEngine.PlayRigidBodyEffect
+ _canRegister = false;
}
public static MethodBase TargetMethod()
@@ -292,16 +143,15 @@ namespace GamecraftModdingAPI.Blocks
public static IEnumerator Prep()
{ //TODO: Don't let the game load until this finishes
- Console.WriteLine("Loading custom catalog...");
- var res = Addressables.LoadContentCatalogAsync("customCatalog.json");
- while (!res.IsDone) yield return Yield.It;
- Console.WriteLine("Loaded custom catalog: " + res.Result.LocatorId);
- Addressables.AddResourceLocator(res.Result);
- /*Console.WriteLine("Loading Cube asset...");
- var loadTask = Addressables.LoadAssetAsync("Assets/Cube.prefab");
- while (!loadTask.IsDone) yield return Yield.It;
- Console.WriteLine("Exception: "+loadTask.OperationException);
- Console.WriteLine("Result: " + loadTask.Result.name);*/
+ 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);
+ }
}
}
}
\ No newline at end of file
diff --git a/GamecraftModdingAPI/Blocks/CustomBlockAttribute.cs b/GamecraftModdingAPI/Blocks/CustomBlockAttribute.cs
new file mode 100644
index 0000000..7c7669f
--- /dev/null
+++ b/GamecraftModdingAPI/Blocks/CustomBlockAttribute.cs
@@ -0,0 +1,85 @@
+using System;
+using DataLoader;
+
+namespace GamecraftModdingAPI.Blocks
+{
+ [AttributeUsage(AttributeTargets.Class)]
+ public class CustomBlockAttribute : Attribute
+ {
+ ///
+ /// Custom block attribute necessary for configuration.
+ ///
+ /// File path to the catalog.json that holds asset references for the custom block
+ /// The path/address to the block's prefab specified in Unity
+ /// The translation key for the block's name
+ /// The path to the inventory sprite for the block, console block by default
+ /// The translation key for the block's description
+ public CustomBlockAttribute(string catalog, string assetPath, string nameKey,
+ string spriteName = "CTR_CommandBlock", string descKey = "")
+ {
+ Catalog = catalog;
+ AssetPath = assetPath;
+ SpriteName = spriteName;
+ NameKey = nameKey;
+ DescKey = descKey;
+ }
+
+ ///
+ /// The location of the catalog.json file used to find assets for this block.
+ ///
+ public string Catalog { get; }
+ ///
+ /// The asset path/address for the block's prefab.
+ ///
+ public string AssetPath { get; }
+ ///
+ /// The name of the sprite used in the inventory.
+ ///
+ public string SpriteName { get; }
+ ///
+ /// The translation key for the block's name.
+ ///
+ public string NameKey { get; }
+ ///
+ /// The translation key for the block's description.
+ ///
+ public string DescKey { get; }
+
+ ///
+ /// The block's type - block, joint, light.
+ ///
+ public CubeType Type { get; set; } = CubeType.Block;
+ ///
+ /// The block's category, so it's treated as a pre-existing functional block.
+ ///
+ public CubeCategory Category { get; set; } = CubeCategory.General;
+ ///
+ /// The block's inventory category.
+ ///
+ public InventoryCategory InventoryCategory { get; set; } = InventoryCategory.Shapes;
+ ///
+ /// The block's mass.
+ ///
+ public float Mass { get; set; } = 1f;
+ ///
+ /// The key of the material properties this block should use.
+ ///
+ public string Material { get; set; } = "Aluminium";
+ ///
+ /// The scaling permission determining what scaling is allowed on this block.
+ ///
+ public ScalingPermission ScalingPermission { get; set; }
+ ///
+ /// The sort index in the inventory.
+ ///
+ public int SortIndex { get; set; }
+ ///
+ /// The default color of the block when placed.
+ ///
+ public BlockColor DefaultColor { get; set; }
+ ///
+ /// The volume of the block.
+ ///
+ public float Volume { get; set; } = 1f;
+ }
+}
\ No newline at end of file
diff --git a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
index 268fdb2..02c7d2c 100644
--- a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
+++ b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
@@ -291,6 +292,13 @@ namespace GamecraftModdingAPI.Tests
.BlockGroup = group;
}).Build();
+ CommandBuilder.Builder("placeCustomBlock", "Places a custom block, needs a custom catalog and assets.")
+ .Action((float x, float y, float z) =>
+ {
+ Logging.CommandLog("Block placed: " +
+ Block.PlaceNew((BlockIDs) 500, new float3(0, 0, 0)));
+ }).Build();
+
GameClient.SetDebugInfo("InstalledMods", InstalledMods);
Block.Placed += (sender, args) =>
Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID);
@@ -361,11 +369,16 @@ namespace GamecraftModdingAPI.Tests
Log.Output("Submitted: " + Window.selected.submittedCode);
})
.Build();
- /*JObject o1 = JObject.Parse(File.ReadAllText(@"Gamecraft_Data\StreamingAssets\aa\Windows\catalog.json"));
- JObject o2 = JObject.Parse(File.ReadAllText(@"customCatalog.json"));
- o1.Merge(o2, new JsonMergeSettings {MergeArrayHandling = MergeArrayHandling.Union});
- File.WriteAllText(@"Gamecraft_Data\StreamingAssets\aa\Windows\catalog.json", o1.ToString());*/
CustomBlock.Prep().RunOn(ExtraLean.UIScheduler);
+ try
+ {
+ CustomBlock.RegisterCustomBlock();
+ Logging.MetaDebugLog("Registered test custom block");
+ }
+ catch (FileNotFoundException)
+ {
+ Logging.MetaDebugLog("Test custom block catalog not found");
+ }
#if TEST
TestRoot.RunTests();
@@ -429,6 +442,18 @@ namespace GamecraftModdingAPI.Tests
return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method;
}
}
+
+ [CustomBlock("customCatalog.json", "Assets/Prefabs/Cube.prefab", "strAluminiumCube", SortIndex = 12)]
+ public class TestBlock : CustomBlock
+ {
+ public TestBlock(EGID id) : base(id)
+ {
+ }
+
+ public TestBlock(uint id) : base(id)
+ {
+ }
+ }
}
#endif
}