using System; using System.Collections.Generic; using System.Linq; using HarmonyLib; using Gamecraft.ColourPalette; using Gamecraft.Wires; using RobocraftX.Blocks; using RobocraftX.Common; using RobocraftX.Physics; using RobocraftX.Rendering; using RobocraftX.Rendering.GPUI; using Svelto.DataStructures; using Svelto.ECS; using Svelto.ECS.EntityStructs; using Svelto.ECS.Experimental; using Svelto.ECS.Hybrid; using Techblox.BuildingDrone; using Techblox.ObjectIDBlockServer; using Unity.Mathematics; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Utility; namespace TechbloxModdingAPI.Blocks.Engines { /// /// Engine for executing general block actions /// public partial class BlockEngine : IApiEngine { public string Name { get; } = "TechbloxModdingAPIBlockGameEngine"; public EntitiesDB entitiesDB { set; private get; } public bool isRemovable => false; public void Dispose() { } public void Ready() { } public Block[] GetConnectedBlocks(EGID blockID) { if (!BlockExists(blockID)) return Array.Empty(); Stack cubeStack = new Stack(); FasterList cubes = new FasterList(10); var coll = entitiesDB.QueryEntities(); foreach (var ((ecoll, count), _) in coll) { for(int i = 0; i < count; i++) { ecoll[i].isProcessed = false; } } //TODO: GetConnectedCubesUtility ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubes, (in GridConnectionsEntityStruct _) => false); var ret = new Block[cubes.count]; for (int i = 0; i < cubes.count; i++) ret[i] = Block.New(cubes[i]); return ret; } public float4 ConvertBlockColor(byte index) => index == byte.MaxValue ? new float4(-1f, -1f, -1f, -1f) : entitiesDB.QueryEntity(index, CommonExclusiveGroups.COLOUR_PALETTE_GROUP).Colour; public OptionalRef GetBlockInfoOptional(Block block) where T : unmanaged, IEntityComponent { return entitiesDB.QueryEntityOptional(block); } public ref T GetBlockInfo(Block block) where T : unmanaged, IEntityComponent { #if DEBUG if (!typeof(BlockTagEntityStruct).IsAssignableFrom(typeof(T)) && block.Exists && block.InitData.Valid) throw new ArgumentException("The block exists but the init data has not been removed!"); #endif return ref entitiesDB.QueryEntityOrDefault(block); } internal ref T GetBlockInfo(EcsObjectBase obj) where T : unmanaged, IEntityComponent { return ref entitiesDB.QueryEntityOrDefault(obj); } public ref T GetBlockInfoViewComponent(Block block) where T : struct, IEntityViewComponent { return ref entitiesDB.QueryEntityOrDefault(block); } internal object GetBlockInfo(Block block, Type type, string name) { var opt = AccessTools.Method(typeof(NativeApiExtensions), "QueryEntityOptional", new[] { typeof(EntitiesDB), typeof(EcsObjectBase), typeof(ExclusiveGroupStruct) }, new[] { type }) .Invoke(null, new object[] { entitiesDB, block, null }); var str = AccessTools.Property(opt.GetType(), "Value").GetValue(opt); return AccessTools.Field(str.GetType(), name).GetValue(str); } internal void SetBlockInfo(Block block, Type type, string name, object value) { var opt = AccessTools.Method(typeof(BlockEngine), "GetBlockInfoOptional", generics: new[] { type }) .Invoke(this, new object[] { block }); var prop = AccessTools.Property(opt.GetType(), "Value"); var str = prop.GetValue(opt); AccessTools.Field(str.GetType(), name).SetValue(str, value); prop.SetValue(opt, str); } public void UpdateDisplayedBlock(EGID id) { if (!BlockExists(id)) return; var pos = entitiesDB.QueryEntity(id); var rot = entitiesDB.QueryEntity(id); var scale = entitiesDB.QueryEntity(id); var skew = entitiesDB.QueryEntity(id); entitiesDB.QueryEntity(id).matrix = math.mul(float4x4.TRS(pos.position, rot.rotation, scale.scale), skew.skewMatrix); entitiesDB.PublishEntityChangeDelayed(id); // Signal a prefab change so it updates the render buffers } internal void UpdatePrefab(Block block, byte material, bool flipped) { var prefabAssetIDOpt = entitiesDB.QueryEntityOptional(block); uint prefabAssetID = prefabAssetIDOpt ? prefabAssetIDOpt.Get().prefabAssetID : uint.MaxValue; if (prefabAssetID == uint.MaxValue) { if (entitiesDB.QueryEntityOptional(block)) //The block exists throw new BlockException("Prefab asset ID not found for block " + block); //Set by the game return; } uint prefabId = PrefabsID.GetOrAddPrefabID((ushort) prefabAssetID, material, 1, flipped); entitiesDB.QueryEntityOrDefault(block).prefabID = prefabId; if (block.Exists) { entitiesDB.PublishEntityChangeDelayed(block.Id); entitiesDB.PublishEntityChangeDelayed(block.Id); ref BuildingActionComponent local = ref entitiesDB.QueryEntity(BuildingDroneUtility .GetLocalBuildingDrone(entitiesDB).ToEGID(entitiesDB)); local.buildAction = BuildAction.ChangeMaterial; local.targetPosition = block.Position; this.entitiesDB.PublishEntityChangeDelayed(local.ID); } //Phyiscs prefab: prefabAssetID, set on block creation from the CubeListData } public void UpdateBlockColor(EGID id) { entitiesDB.PublishEntityChange(id); } public bool BlockExists(EGID blockID) { return entitiesDB.Exists(blockID); } public SimBody[] GetSimBodiesFromID(byte id) { var ret = new FasterList(4); var (oids, tags, count) = entitiesDB.QueryEntities(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP); EGIDMapper? connections = null; for (int i = 0; i < count; i++) { if (oids[i].objectId != id) continue; var tag = tags[i]; if (!connections.HasValue) //Would need reflection to get the group from the build group otherwise connections = entitiesDB.QueryMappedEntities(tag.ID.groupID); var rid = connections.Value.Entity(tag.ID.entityID).machineRigidBodyId; foreach (var rb in ret) { if (rb.Id.entityID == rid) goto DUPLICATE; //Multiple Object Identifiers on one rigid body } ret.Add(new SimBody(rid)); DUPLICATE: ; } return ret.ToArray(); } public SimBody[] GetConnectedSimBodies(uint id) { var (joints, count) = entitiesDB.QueryEntities(MachineSimulationGroups.JOINTS_GROUP); var list = new FasterList(4); for (int i = 0; i < count; i++) { ref var joint = ref joints[i]; if (joint.isBroken) continue; if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB)); else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA)); } return list.ToArray(); } public SimBody[] GetClusterBodies(uint cid) { var groups = entitiesDB.QueryEntities(); var bodies = new HashSet(); foreach (var ((coll, count), _) in groups) { for (var index = 0; index < count; index++) { var conn = coll[index]; if (conn.clusterId == cid) bodies.Add(conn.machineRigidBodyId); } } return bodies.Select(id => new SimBody(id, cid)).ToArray(); } public EGID? FindBlockEGID(uint id) { var groups = entitiesDB.FindGroups(); foreach (ExclusiveGroupStruct group in groups) { if (entitiesDB.Exists(id, group)) return new EGID(id, group); } return null; } public Cluster GetCluster(uint sbid) { var groups = entitiesDB.QueryEntities(); foreach (var ((coll, count), _) in groups) { for (var index = 0; index < count; index++) { var conn = coll[index]; //Static blocks don't have a cluster ID but the cluster destruction manager should have one if (conn.machineRigidBodyId == sbid && conn.clusterId != uint.MaxValue) return new Cluster(conn.clusterId); } } return null; } public Block[] GetBodyBlocks(uint sbid) { var groups = entitiesDB.FindGroups(); groups = new QueryGroups(groups).Except(CommonExclusiveGroups.DISABLED_JOINTS_IN_SIM_GROUP).Evaluate().result; var set = new HashSet(); foreach (var ((coll, tags, count), _) in entitiesDB.QueryEntities(groups)) { for (var index = 0; index < count; index++) { var conn = coll[index]; if (conn.machineRigidBodyId == sbid) set.Add(Block.New(tags[index].ID)); } } return set.ToArray(); } public ObjectID[] GetObjectIDsFromID(byte id) { if (!entitiesDB.HasAny(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP)) return Array.Empty(); var ret = new FasterList(4); var (oids, tags, count) = entitiesDB.QueryEntities(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP); for (var index = 0; index < count; index++) { if (oids[index].objectIDToTrigger == id) ret.Add(new ObjectID(tags[index].ID)); } return ret.ToArray(); } } }