@@ -171,7 +171,7 @@ namespace GamecraftModdingAPI | |||
egid = new EGID(id, group.Value); | |||
if (typeToGroup.TryGetValue(type, out var gr) | |||
&& gr.All(egs => egs != group.Value)) //If this subclass has a specific group, then use that - so Block should still work | |||
throw new BlockTypeException($"Incompatible block type! Type {type.Name} belongs to group {gr.Select(g => g.ToString()).Aggregate((a, b) => a + ", " + b)} instead of {group.Value}"); | |||
throw new BlockTypeException($"Incompatible block type! Type {type.Name} belongs to group {gr.Select(g => ((uint)g).ToString()).Aggregate((a, b) => a + ", " + b)} instead of {(uint)group.Value}"); | |||
} | |||
if (initializers.TryGetValue(type, out var func)) | |||
@@ -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."); | |||
} | |||
@@ -227,7 +227,7 @@ namespace GamecraftModdingAPI.Blocks | |||
for (int i = 0; i < joints.count; i++) | |||
{ | |||
ref var joint = ref joints.buffer[i]; | |||
if (joint.jointState == JointState.Broken) continue; | |||
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)); | |||
} | |||
@@ -71,8 +71,9 @@ namespace GamecraftModdingAPI.Blocks | |||
GlassConeSegment, | |||
GlassCylinder, | |||
GlassSphere, | |||
Lever, //63 - two IDs skipped | |||
PlayerSpawn = 66, //Crashes without special handling | |||
Lever, //63 | |||
WoodenSlatsDoor = 65, | |||
PlayerSpawn, //Crashes without special handling | |||
SmallSpawn, | |||
MediumSpawn, | |||
LargeSpawn, | |||
@@ -86,10 +87,17 @@ namespace GamecraftModdingAPI.Blocks | |||
DampedSpring, | |||
ServoPiston, | |||
StepperPiston, | |||
PneumaticPiston, | |||
PneumaticPiston, //80 | |||
PneumaticHinge, | |||
PneumaticAxle, //82 | |||
PilotSeat = 90, //Might crash | |||
PneumaticAxle, | |||
WindowedDoor, | |||
Bench, | |||
Chair, | |||
Stool, | |||
DampedHingeSpring, | |||
PlainGlassDoor, | |||
PlainWoodenDoor, | |||
PilotSeat, //Might crash | |||
PassengerSeat, | |||
PilotControls, | |||
GrassCube, | |||
@@ -148,8 +156,9 @@ namespace GamecraftModdingAPI.Blocks | |||
WoodCylinder, | |||
WoodHemisphere, | |||
WoodSphere, | |||
BrickCube, //149 | |||
BrickSlicedCube = 151, | |||
BrickCube, | |||
DampedAxleSpring, //150 | |||
BrickSlicedCube, | |||
BrickSlope, | |||
BrickCorner, | |||
ConcreteCube, | |||
@@ -355,6 +364,7 @@ namespace GamecraftModdingAPI.Blocks | |||
HexNetCylinder = 797, | |||
HexNetHemisphere, | |||
HexNetSphere, | |||
HexNetTubeCorner //800 | |||
HexNetTubeCorner, //800 | |||
CenterOfMassBlock = 1346 | |||
} | |||
} |
@@ -1,6 +1,7 @@ | |||
using System; | |||
using Gamecraft.Wires; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI; | |||
using GamecraftModdingAPI.Tests; | |||
@@ -60,19 +61,31 @@ namespace GamecraftModdingAPI.Blocks | |||
Assert.Errorless(() => { b = newBlock.Specialise<Piston>(); }, "Block.Specialize<Piston>() raised an exception: ", "Block.Specialize<Piston>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<Piston>() returned null, possibly because it failed silently.", "Specialized Piston is not null.")) return; | |||
if (!Assert.CloseTo(b.MaximumExtension, 1.01f, $"Piston.MaximumExtension {b.MaximumExtension} does not equal default value, possibly because it failed silently.", "Piston.MaximumExtension is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.MaximumForce, 750f, $"Piston.MaximumForce {b.MaximumForce} does not equal default value, possibly because it failed silently.", "Piston.MaximumForce is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.MaximumForce, 1.0f, $"Piston.MaximumForce {b.MaximumForce} does not equal default value, possibly because it failed silently.", "Piston.MaximumForce is close enough to default.")) return; | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestServo() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.ServoAxle, Unity.Mathematics.float3.zero + 1); | |||
Servo b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = newBlock.Specialise<Servo>(); }, "Block.Specialize<Servo>() raised an exception: ", "Block.Specialize<Servo>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<Servo>() returned null, possibly because it failed silently.", "Specialized Servo is not null.")) return; | |||
if (!Assert.CloseTo(b.MaximumAngle, 180f, $"Servo.MaximumAngle {b.MaximumAngle} does not equal default value, possibly because it failed silently.", "Servo.MaximumAngle is close enough to default.")) return; | |||
Block newBlock = Block.PlaceNew(BlockIDs.ServoAxle, Unity.Mathematics.float3.zero + 1); | |||
Servo b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = newBlock.Specialise<Servo>(); }, "Block.Specialize<Servo>() raised an exception: ", "Block.Specialize<Servo>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<Servo>() returned null, possibly because it failed silently.", "Specialized Servo is not null.")) return; | |||
if (!Assert.CloseTo(b.MaximumAngle, 180f, $"Servo.MaximumAngle {b.MaximumAngle} does not equal default value, possibly because it failed silently.", "Servo.MaximumAngle is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.MinimumAngle, -180f, $"Servo.MinimumAngle {b.MinimumAngle} does not equal default value, possibly because it failed silently.", "Servo.MinimumAngle is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.MaximumForce, 750f, $"Servo.MaximumForce {b.MaximumForce} does not equal default value, possibly because it failed silently.", "Servo.MaximumForce is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.MaximumForce, 60f, $"Servo.MaximumForce {b.MaximumForce} does not equal default value, possibly because it failed silently.", "Servo.MaximumForce is close enough to default.")) return; | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestDampedSpring() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.DampedSpring, Unity.Mathematics.float3.zero + 1); | |||
DampedSpring b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = newBlock.Specialise<DampedSpring>(); }, "Block.Specialize<Servo>() raised an exception: ", "Block.Specialize<DampedSpring>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<DampedSpring>() returned null, possibly because it failed silently.", "Specialized DampedSpring is not null.")) return; | |||
if (!Assert.CloseTo(b.Stiffness, 1.0f, $"DampedSpring.Stiffness {b.Stiffness} does not equal default value, possibly because it failed silently.", "DampedSpring.Stiffness is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.Damping, 0.1f, $"DampedSpring.Damping {b.Damping} does not equal default value, possibly because it failed silently.", "DampedSpring.Damping is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.MaxExtension, 0.3f, $"DampedSpring.MaxExtension {b.MaxExtension} does not equal default value, possibly because it failed silently.", "DampedSpring.MaxExtension is close enough to default.")) return; | |||
} | |||
[APITestCase(TestType.Game)] | |||
@@ -119,6 +132,13 @@ namespace GamecraftModdingAPI.Blocks | |||
if (!Assert.Errorless(() => { newWire = b.Connect(0, target, 0);})) return; | |||
if (!Assert.NotNull(newWire, "SignalingBlock.Connect(...) returned null, possible because it failed silently.", "SignalingBlock.Connect(...) returned a non-null value.")) return; | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestSpecialiseError() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.Bench, new float3(1, 1, 1)); | |||
if (Assert.Errorful<BlockTypeException>(() => newBlock.Specialise<MusicBlock>(), "Block.Specialise<MusicBlock>() was expected to error on a bench block.", "Block.Specialise<MusicBlock>() errored as expected for a bench block.")) return; | |||
} | |||
} | |||
#endif | |||
} |
@@ -0,0 +1,173 @@ | |||
using System; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Reflection; | |||
using HarmonyLib; | |||
using DataLoader; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Rendering; | |||
using Svelto.ECS; | |||
using Svelto.Tasks; | |||
using UnityEngine; | |||
using UnityEngine.AddressableAssets; | |||
using Material = UnityEngine.Material; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class CustomBlock : Block | |||
{ | |||
private static ushort nextID = 500; | |||
/// <summary> | |||
/// Key: Prefab path | |||
/// </summary> | |||
private static Dictionary<string, Type> _customBlocks = new Dictionary<string, Type>(); | |||
private static bool _canRegister = true; | |||
/// <summary> | |||
/// Register a custom block type. Call it as soon as possible (in OnApplicationStart()).<br /> | |||
/// 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. | |||
/// </summary> | |||
/// <typeparam name="T">The custom block type</typeparam> | |||
public static void RegisterCustomBlock<T>() 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<CustomBlockAttribute>(); | |||
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); | |||
} | |||
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] | |||
public static class MaterialCopyPatch | |||
{ | |||
private static Material[] materials; | |||
public static void Prefix(List<PrefabData> prefabData, IList<GameObject> prefabs) | |||
{ | |||
for (var index = 0; index < prefabs.Count; index++) | |||
{ | |||
if (prefabData[index].prefabName == "ConsoleBlock") | |||
materials = prefabs[index].GetComponentsInChildren<MeshRenderer>()[0].sharedMaterials; | |||
} | |||
for (var index = 0; index < prefabs.Count; index++) | |||
{ | |||
if (_customBlocks.ContainsKey(prefabData[index].prefabName)) //This is a custom block | |||
prefabs[index].GetComponentsInChildren<MeshRenderer>()[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<CubeListData>((int) BlockIDs.AluminiumCube); | |||
foreach (var (key, type) in _customBlocks) | |||
{ | |||
var attr = type.GetCustomAttribute<CustomBlockAttribute>(); | |||
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}, | |||
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<CubeListData>().Add(cld.ID.ToString(), cld); //The registration needs to happen after the ID has been set | |||
dataDB.GetFasterValues<CubeListData>().Add(cld.ID, cld); //So can't use the builtin method to create a CubeListData | |||
} | |||
_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<CubeCategory, IBlockBuilder>) | |||
AccessTools.Field(__instance.GetType(), "_blockBuilders").GetValue(__instance); | |||
builders.Add((CubeCategory) 1000, new BlockBuilder<StandardBlockEntityDescriptor>(Group)); | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
return AccessTools.Method(typeof(BlockEntityFactory), "ParseDataDB"); | |||
} | |||
}*/ | |||
internal static IEnumerator Prepare() | |||
{ //Should be pretty quick | |||
foreach (var type in _customBlocks.Values) | |||
{ | |||
var attr = type.GetCustomAttribute<CustomBlockAttribute>(); | |||
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 static void OnBlockFactoryObtained(BlockEntityFactory factory) | |||
{ | |||
var builders = (Dictionary<CubeCategory, IBlockBuilder>) | |||
AccessTools.Field(factory.GetType(), "_blockBuilders").GetValue(factory); | |||
builders.Add((CubeCategory) 1000, new BlockBuilder<StandardBlockEntityDescriptor>(Group)); | |||
}*/ | |||
} | |||
} |
@@ -0,0 +1,85 @@ | |||
using System; | |||
using DataLoader; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
[AttributeUsage(AttributeTargets.Class)] | |||
public class CustomBlockAttribute : Attribute | |||
{ | |||
/// <summary> | |||
/// Custom block attribute necessary for configuration. | |||
/// </summary> | |||
/// <param name="catalog">File path to the catalog.json that holds asset references for the custom block</param> | |||
/// <param name="assetPath">The path/address to the block's prefab specified in Unity</param> | |||
/// <param name="nameKey">The translation key for the block's name</param> | |||
/// <param name="spriteName">The path to the inventory sprite for the block, console block by default</param> | |||
/// <param name="descKey">The translation key for the block's description</param> | |||
public CustomBlockAttribute(string catalog, string assetPath, string nameKey, | |||
string spriteName = "CTR_CommandBlock", string descKey = "") | |||
{ | |||
Catalog = catalog; | |||
AssetPath = assetPath; | |||
SpriteName = spriteName; | |||
NameKey = nameKey; | |||
DescKey = descKey; | |||
} | |||
/// <summary> | |||
/// The location of the catalog.json file used to find assets for this block. | |||
/// </summary> | |||
public string Catalog { get; } | |||
/// <summary> | |||
/// The asset path/address for the block's prefab. | |||
/// </summary> | |||
public string AssetPath { get; } | |||
/// <summary> | |||
/// The name of the sprite used in the inventory. | |||
/// </summary> | |||
public string SpriteName { get; } | |||
/// <summary> | |||
/// The translation key for the block's name. | |||
/// </summary> | |||
public string NameKey { get; } | |||
/// <summary> | |||
/// The translation key for the block's description. | |||
/// </summary> | |||
public string DescKey { get; } | |||
/// <summary> | |||
/// The block's type - block, joint, light. | |||
/// </summary> | |||
public CubeType Type { get; set; } = CubeType.Block; | |||
/// <summary> | |||
/// The block's category, so it's treated as a pre-existing functional block. | |||
/// </summary> | |||
public CubeCategory Category { get; set; } = CubeCategory.General; | |||
/// <summary> | |||
/// The block's inventory category. | |||
/// </summary> | |||
public InventoryCategory InventoryCategory { get; set; } = InventoryCategory.Shapes; | |||
/// <summary> | |||
/// The block's mass. | |||
/// </summary> | |||
public float Mass { get; set; } = 1f; | |||
/// <summary> | |||
/// The key of the material properties this block should use. | |||
/// </summary> | |||
public string Material { get; set; } = "Aluminium"; | |||
/// <summary> | |||
/// The scaling permission determining what scaling is allowed on this block. | |||
/// </summary> | |||
public ScalingPermission ScalingPermission { get; set; } | |||
/// <summary> | |||
/// The sort index in the inventory. | |||
/// </summary> | |||
public int SortIndex { get; set; } | |||
/// <summary> | |||
/// The default color of the block when placed. | |||
/// </summary> | |||
public BlockColor DefaultColor { get; set; } | |||
/// <summary> | |||
/// The volume of the block. | |||
/// </summary> | |||
public float Volume { get; set; } = 1f; | |||
} | |||
} |
@@ -19,10 +19,10 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public float MaxForce | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (DampedSpringReadOnlyStruct dsrs) => dsrs.maxForce); | |||
get => BlockEngine.GetBlockInfo(this, (DampedSpringReadOnlyStruct dsrs) => dsrs.springFrequency); | |||
set => BlockEngine.SetBlockInfo(this, | |||
(ref DampedSpringReadOnlyStruct dsrs, float val) => dsrs.maxForce = val, value); | |||
(ref DampedSpringReadOnlyStruct dsrs, float val) => dsrs.springFrequency = val, value); | |||
} | |||
/// <summary> | |||
@@ -39,10 +39,21 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public float Damping | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (LinearJointForcesReadOnlyStruct ljf) => ljf.dampingForceMagnitude); | |||
get => BlockEngine.GetBlockInfo(this, (DampedSpringReadOnlyStruct ljf) => ljf.springDamping); | |||
set => BlockEngine.SetBlockInfo(this, | |||
(ref LinearJointForcesReadOnlyStruct ljf, float val) => ljf.dampingForceMagnitude = val, value); | |||
(ref DampedSpringReadOnlyStruct ljf, float val) => ljf.springDamping = val, value); | |||
} | |||
/// <summary> | |||
/// The spring's maximum extension. | |||
/// </summary> | |||
public float MaxExtension | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (DampedSpringReadOnlyStruct ljf) => ljf.maxExtent); | |||
set => BlockEngine.SetBlockInfo(this, | |||
(ref DampedSpringReadOnlyStruct ljf, float val) => ljf.maxExtent = val, value); | |||
} | |||
} | |||
} |
@@ -40,11 +40,11 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public float MaximumForce | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxForce); | |||
get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.pistonVelocity); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxForce = val, value); | |||
BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.pistonVelocity = val, value); | |||
} | |||
} | |||
} | |||
@@ -52,11 +52,11 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public float MaximumForce | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxForce); | |||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.servoVelocity); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxForce = val, value); | |||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.servoVelocity = val, value); | |||
} | |||
} | |||
@@ -35,10 +35,8 @@ namespace GamecraftModdingAPI.Blocks | |||
BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) => | |||
{ | |||
tbds.textCurrent.Set(val); | |||
tbds.textStored.Set(val); | |||
tbds.textStored.Set(val, true); | |||
}, value); | |||
BlockEngine.SetBlockInfo(this, | |||
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockStringContent.Set(val), value); | |||
} | |||
} | |||
@@ -54,8 +52,6 @@ namespace GamecraftModdingAPI.Blocks | |||
if (value == null) value = ""; | |||
BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) => | |||
tbds.textBlockID.Set(val), value); | |||
BlockEngine.SetBlockInfo(this, | |||
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockID.Set(val), value); | |||
} | |||
} | |||
} | |||
@@ -79,9 +79,13 @@ | |||
<HintPath>..\ref\Gamecraft_Data\Managed\DDNA.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\DDNA.dll</HintPath> | |||
</Reference> | |||
<Reference Include="FMOD"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\FMOD.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\FMOD.dll</HintPath> | |||
<Reference Include="FMODUnity"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\FMODUnity.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\FMODUnity.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Facepunch.Steamworks.Win64"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath> | |||
</Reference> | |||
<Reference Include="FullGame"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\FullGame.dll</HintPath> | |||
@@ -267,6 +271,10 @@ | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Music.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Music.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Gamecraft.NetStrings"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.NetStrings.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.NetStrings.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Gamecraft.PerformanceWarnings"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.PerformanceWarnings.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.PerformanceWarnings.dll</HintPath> | |||
@@ -287,6 +295,10 @@ | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Projectiles.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Projectiles.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Gamecraft.Serialization"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Serialization.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Serialization.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Gamecraft.Tweaks"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.dll</HintPath> | |||
@@ -475,6 +487,10 @@ | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.NetworkEntityStream.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.NetworkEntityStream.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.Multiplayer.Serializers"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.Serializers.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.Serializers.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.MultiplayerInput"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.MultiplayerInput.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.MultiplayerInput.dll</HintPath> | |||
@@ -519,10 +535,6 @@ | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.SaveGameDialog.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.SaveGameDialog.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.Serializers"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Serializers.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Serializers.dll</HintPath> | |||
</Reference> | |||
<Reference Include="RobocraftX.Services"> | |||
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Services.dll</HintPath> | |||
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Services.dll</HintPath> | |||
@@ -1,12 +1,14 @@ | |||
using System; | |||
using System.Reflection; | |||
using GamecraftModdingAPI.Blocks; | |||
using HarmonyLib; | |||
using RobocraftX; | |||
using RobocraftX.Schedulers; | |||
using RobocraftX.Services; | |||
using Svelto.Context; | |||
using Svelto.Tasks.ExtraLean; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI.Events; | |||
using GamecraftModdingAPI.Tasks; | |||
@@ -89,6 +91,7 @@ namespace GamecraftModdingAPI | |||
AsyncUtils.Init(); | |||
GamecraftModdingAPI.App.Client.Init(); | |||
GamecraftModdingAPI.App.Game.Init(); | |||
CustomBlock.Prepare().RunOn(ExtraLean.UIScheduler); | |||
// init UI | |||
Interface.IMGUI.Constants.Init(); | |||
Interface.IMGUI.IMGUIManager.Init(); | |||
@@ -119,6 +119,25 @@ namespace GamecraftModdingAPI.Tests | |||
return true; | |||
} | |||
public static bool Errorful<T>(Action tryThis, string err = null, string success = null) where T : Exception | |||
{ | |||
if (err == null) err = $"{tryThis} was expected to error but completed without errors."; | |||
if (success == null) success = $"{tryThis} completed with an expected error."; | |||
try | |||
{ | |||
tryThis(); | |||
} | |||
catch (T e) | |||
{ | |||
TestRoot.TestsPassed = true; | |||
Log(PASS + success + " " + e.GetType().Name + ": " + e.Message); | |||
return true; | |||
} | |||
Log(FAIL + err); | |||
TestRoot.TestsPassed = false; | |||
return false; | |||
} | |||
public static bool CloseTo(float a, float b, string err = null, string success = null, float delta = float.Epsilon) | |||
{ | |||
if (err == null) err = $"{a} is not within {delta} of {b}."; | |||
@@ -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; | |||
@@ -17,6 +18,9 @@ using RobocraftX.SimulationModeState; | |||
using RobocraftX.FrontEnd; | |||
using Unity.Mathematics; | |||
using UnityEngine; | |||
using RobocraftX.Schedulers; | |||
using Svelto.Tasks.ExtraLean; | |||
using uREPL; | |||
using GamecraftModdingAPI.Commands; | |||
using GamecraftModdingAPI.Events; | |||
@@ -296,6 +300,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<TestBlock>((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); | |||
@@ -356,7 +367,7 @@ namespace GamecraftModdingAPI.Tests | |||
Logging.Log("Compatible GamecraftScripting detected"); | |||
} | |||
// Interface test | |||
/*Interface.IMGUI.Group uiGroup = new Group(new Rect(20, 20, 200, 500), "GamecraftModdingAPI_UITestGroup", true); | |||
Interface.IMGUI.Group uiGroup = new Group(new Rect(20, 20, 200, 500), "GamecraftModdingAPI_UITestGroup", true); | |||
Interface.IMGUI.Button button = new Button("TEST"); | |||
button.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");}; | |||
Interface.IMGUI.Button button2 = new Button("TEST2"); | |||
@@ -380,7 +391,27 @@ namespace GamecraftModdingAPI.Tests | |||
uiImg.Enabled = true; | |||
Logging.MetaDebugLog($"Got blue bg asset {handle.Result}"); | |||
}; | |||
*/ | |||
CommandBuilder.Builder("enableCompletions") | |||
.Action(() => | |||
{ | |||
var p = Window.selected.main.parameters; | |||
p.useCommandCompletion = true; | |||
p.useMonoCompletion = true; | |||
p.useGlobalClassCompletion = true; | |||
Log.Output("Submitted: " + Window.selected.submittedCode); | |||
}) | |||
.Build(); | |||
try | |||
{ | |||
CustomBlock.RegisterCustomBlock<TestBlock>(); | |||
Logging.MetaDebugLog("Registered test custom block"); | |||
} | |||
catch (FileNotFoundException) | |||
{ | |||
Logging.MetaDebugLog("Test custom block catalog not found"); | |||
} | |||
#if TEST | |||
TestRoot.RunTests(); | |||
#endif | |||
@@ -443,6 +474,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 | |||
} |
@@ -43,40 +43,24 @@ namespace GamecraftModdingAPI.Utility | |||
{ | |||
get | |||
{ | |||
sound.getParameterValue(key, out float val, out float finalVal); | |||
sound.getParameterByName(key, out float val, out float finalVal); | |||
return val; | |||
} | |||
set => sound.setParameterValue(key, value); | |||
set => sound.setParameterByName(key, value); | |||
} | |||
public float this[int index] | |||
public float this[PARAMETER_ID index] | |||
{ | |||
get | |||
{ | |||
sound.getParameterValueByIndex(index, out float val, out float finalVal); | |||
sound.getParameterByID(index, out float val, out float finalVal); | |||
return val; | |||
} | |||
set => sound.setParameterValueByIndex(index, value); | |||
set => sound.setParameterByID(index, value); | |||
} | |||
public string[] Parameters | |||
{ | |||
get | |||
{ | |||
sound.getParameterCount(out int count); | |||
string[] parameters = new string[count]; | |||
for (int i = 0; i < count; i++) | |||
{ | |||
sound.getParameterByIndex(i, out ParameterInstance param); | |||
param.getDescription(out PARAMETER_DESCRIPTION desc); | |||
parameters[i] = desc.name; | |||
} | |||
return parameters; | |||
} | |||
} | |||
public float3 Position | |||
{ | |||
get | |||