Compare commits

..

No commits in common. "master" and "v2.0.0" have entirely different histories.

101 changed files with 2791 additions and 8737 deletions

View file

@ -5,7 +5,7 @@ import re
# this assumes a mostly semver-complient version number
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Increment TechbloxModdingAPI version")
parser = argparse.ArgumentParser(description="Increment GamecraftModdingAPI version")
parser.add_argument('version', metavar="VN", type=str, help="The version number to increment, or the index of the number (zero-indexed).")
args = parser.parse_args()
@ -28,12 +28,12 @@ if __name__ == "__main__":
old_version = ""
new_version = ""
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "r") as xmlFile:
print("Parsing TechbloxModdingAPI.csproj")
with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "r") as xmlFile:
print("Parsing GamecraftModdingAPI.csproj")
fileStr = xmlFile.read()
versionMatch = re.search(r"<Version>(.+)</Version>", fileStr)
if versionMatch is None:
print("Unable to find version number in TechbloxModdingAPI.csproj")
print("Unable to find version number in GamecraftModdingAPI.csproj")
exit(1)
old_version = versionMatch.group(1)
versionList = old_version.split(".")
@ -53,7 +53,7 @@ if __name__ == "__main__":
print(new_version)
newFileContents = fileStr.replace("<Version>"+old_version+"</Version>", "<Version>"+new_version+"</Version>")
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "w") as xmlFile:
with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "w") as xmlFile:
print("Writing new version to project file")
xmlFile.write(newFileContents)

View file

@ -3,24 +3,15 @@
import argparse
from pathlib import Path, PurePath
import re
import os
DLL_EXCLUSIONS_REGEX = r"(System|Microsoft|Mono|IronPython|DiscordRPC|IllusionInjector|IllusionPlugin|netstandard)\."
DLL_EXCLUSIONS_REGEX = r"(System|Microsoft|Mono|IronPython|DiscordRPC)\."
def getAssemblyReferences(path):
asmDir = Path(path)
result = list()
addedPath = ""
if not asmDir.exists():
addedPath = "../"
asmDir = Path(addedPath + path)
for child in asmDir.iterdir():
if child.is_file() and re.search(DLL_EXCLUSIONS_REGEX, str(child)) is None and str(child).lower().endswith(".dll"):
childstr = str(child)
childstr = os.path.relpath(childstr, addedPath).replace("\\", "/")
result.append(childstr)
result.sort(key=str.lower)
result = [path + "/IllusionInjector.dll", path + "/IllusionPlugin.dll"] + result # Always put it on top
if child.is_file() and re.search(DLL_EXCLUSIONS_REGEX, str(child), re.I) is None and str(child).lower().endswith(".dll"):
result.append(str(child).replace("\\", "/"))
return result
def buildReferencesXml(path):
@ -41,7 +32,7 @@ if __name__ == "__main__":
args = parser.parse_args()
print("Building Assembly references")
asmXml = buildReferencesXml("../ref_TB/Techblox_Data/Managed")
asmXml = buildReferencesXml("../ref/TechbloxPreview_Data/Managed")
# print(asmXml)
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "r") as xmlFile:
@ -53,7 +44,7 @@ if __name__ == "__main__":
if depsStart is None or depsEnd is None:
print("Unable to find dependency XML comments, aborting!")
exit(1)
newFileStr = fileStr[:depsStart.start() - 1] + "\n" + asmXml + "\n" + fileStr[depsEnd.end() + 1:]
newFileStr = fileStr[:depsStart.start()] + "\n" + asmXml + "\n" + fileStr[depsEnd.end() + 1:]
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "w") as xmlFile:
print("Writing Assembly references")
xmlFile.write(newFileStr)

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="mscorlib" publicKeyToken="b77a5c561934e089" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View file

@ -1,158 +0,0 @@
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Gamecraft.Tweaks;
using RobocraftX.Common;
using Svelto.ECS;
namespace CodeGenerator
{
public class BlockClassGenerator
{
public void Generate(string name, string group = null, Dictionary<string, string> renames = null, params Type[] types)
{
if (group is null)
{
group = GetGroup(name) + "_BLOCK_GROUP";
if (typeof(CommonExclusiveGroups).GetFields().All(field => field.Name != group))
group = GetGroup(name) + "_BLOCK_BUILD_GROUP";
}
if (!group.Contains('.'))
group = "CommonExclusiveGroups." + group;
var codeUnit = new CodeCompileUnit();
var ns = new CodeNamespace("TechbloxModdingAPI.Blocks");
ns.Imports.Add(new CodeNamespaceImport("RobocraftX.Common"));
ns.Imports.Add(new CodeNamespaceImport("Svelto.ECS"));
var cl = new CodeTypeDeclaration(name);
//cl.BaseTypes.Add(baseClass != null ? new CodeTypeReference(baseClass) : new CodeTypeReference("Block"));
cl.BaseTypes.Add(new CodeTypeReference("SignalingBlock"));
cl.Members.Add(new CodeConstructor
{
Parameters = {new CodeParameterDeclarationExpression("EGID", "egid")},
Comments =
{
_start, new CodeCommentStatement($"Constructs a(n) {name} object representing an existing block.", true), _end
},
BaseConstructorArgs = {new CodeVariableReferenceExpression("egid")},
Attributes = MemberAttributes.Public | MemberAttributes.Final
});
cl.Members.Add(new CodeConstructor
{
Parameters =
{
new CodeParameterDeclarationExpression(typeof(uint), "id")
},
Comments =
{
_start, new CodeCommentStatement($"Constructs a(n) {name} object representing an existing block.", true), _end
},
BaseConstructorArgs =
{
new CodeObjectCreateExpression("EGID", new CodeVariableReferenceExpression("id"),
new CodeVariableReferenceExpression(group))
},
Attributes = MemberAttributes.Public | MemberAttributes.Final
});
foreach (var type in types)
{
GenerateProperties(cl, type, name, renames);
}
ns.Types.Add(cl);
codeUnit.Namespaces.Add(ns);
var provider = CodeDomProvider.CreateProvider("CSharp");
var path = $@"../../../../TechbloxModdingAPI/Blocks/{name}.cs";
using (var sw = new StreamWriter(path))
{
provider.GenerateCodeFromCompileUnit(codeUnit, sw, new CodeGeneratorOptions {BracingStyle = "C"});
}
File.WriteAllLines(path,
File.ReadAllLines(path).SkipWhile(line => line.StartsWith("//") || line.Length == 0));
}
private static string GetGroup(string name)
{
var ret = "";
foreach (var ch in name)
{
if (char.IsUpper(ch) && ret.Length > 0)
ret += "_" + ch;
else
ret += char.ToUpper(ch);
}
return ret;
}
private void GenerateProperties(CodeTypeDeclaration cl, Type type, string baseClass,
Dictionary<string, string> renames)
{
if (!typeof(IEntityComponent).IsAssignableFrom(type))
throw new ArgumentException("Type must be an entity component");
bool reflection = type.IsNotPublic;
var reflectedType = new CodeSnippetExpression($"HarmonyLib.AccessTools.TypeByName(\"{type.FullName}\")");
foreach (var field in type.GetFields())
{
var attr = field.GetCustomAttribute<TweakableStatAttribute>();
if (renames == null || !renames.TryGetValue(field.Name, out var propName))
{
propName = field.Name;
if (attr != null)
propName = attr.propertyName;
}
propName = char.ToUpper(propName[0]) + propName.Substring(1);
var getStruct = new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"),
"GetBlockInfo", new CodeTypeReference(type)),
new CodeThisReferenceExpression());
CodeExpression structFieldReference = new CodeFieldReferenceExpression(getStruct, field.Name);
CodeExpression reflectedGet = new CodeCastExpression(field.FieldType, new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"),
"GetBlockInfo"),
new CodeThisReferenceExpression(), reflectedType, new CodePrimitiveExpression(field.Name)));
CodeExpression reflectedSet = new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"),
"SetBlockInfo"),
new CodeThisReferenceExpression(), reflectedType, new CodePrimitiveExpression(field.Name),
new CodePropertySetValueReferenceExpression());
cl.Members.Add(new CodeMemberProperty
{
Name = propName,
HasGet = true,
HasSet = true,
GetStatements =
{
new CodeMethodReturnStatement(reflection ? reflectedGet : structFieldReference)
},
SetStatements =
{
reflection
? (CodeStatement)new CodeExpressionStatement(reflectedSet)
: new CodeAssignStatement(structFieldReference, new CodePropertySetValueReferenceExpression())
},
Type = new CodeTypeReference(field.FieldType),
Attributes = MemberAttributes.Public | MemberAttributes.Final,
Comments =
{
_start,
new CodeCommentStatement($"Gets or sets the {baseClass}'s {propName} property." +
$" {(attr != null ? "Tweakable stat." : "May not be saved.")}",
true),
_end
}
});
}
}
private static readonly CodeCommentStatement _start = new CodeCommentStatement("<summary>", true);
private static readonly CodeCommentStatement _end = new CodeCommentStatement("</summary>", true);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,53 +0,0 @@
using System.Collections.Generic;
using HarmonyLib;
using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.GroupTags;
using RobocraftX.PilotSeat;
using Svelto.ECS;
using Techblox.EngineBlock;
using Techblox.ServoBlocksServer;
using Techblox.WheelRigBlock;
namespace CodeGenerator
{
internal class Program
{
public static void Main(string[] args)
{
GenerateBlockClasses();
}
private static void GenerateBlockClasses()
{
var bcg = new BlockClassGenerator();
bcg.Generate("Engine", null, new Dictionary<string, string>
{
{ "engineOn", "On" }
}, AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), // Simulation time properties
typeof(EngineBlockTweakableComponent), typeof(EngineBlockReadonlyComponent));
bcg.Generate("DampedSpring", "DAMPEDSPRING_BLOCK_GROUP", new Dictionary<string, string>
{
{"maxExtent", "MaxExtension"}
},
typeof(TweakableJointDampingComponent), typeof(DampedSpringReadOnlyStruct));
bcg.Generate("LogicGate", "LOGIC_BLOCK_GROUP");
bcg.Generate("Servo", types: typeof(ServoReadOnlyTweakableComponent), renames: new Dictionary<string, string>
{
{"minDeviation", "MinimumAngle"},
{"maxDeviation", "MaximumAngle"},
{"servoVelocity", "MaximumForce"}
});
bcg.Generate("WheelRig", "WHEELRIG_BLOCK_BUILD_GROUP", null,
typeof(WheelRigTweakableStruct), typeof(WheelRigReadOnlyStruct),
typeof(WheelRigSteerableTweakableStruct), typeof(WheelRigSteerableReadOnlyStruct));
bcg.Generate("Seat", "RobocraftX.PilotSeat.SeatGroups.PILOTSEAT_BLOCK_BUILD_GROUP", null, typeof(SeatFollowCamComponent), typeof(SeatReadOnlySettingsComponent));
bcg.Generate("Piston", null, new Dictionary<string, string>
{
{"pistonVelocity", "MaximumForce"}
}, typeof(PistonReadOnlyStruct));
bcg.Generate("Motor", null, null, typeof(MotorReadOnlyStruct));
//bcg.Generate("ObjectID", "ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP", null, typeof(ObjectIDTweakableComponent));
}
}
}

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Lib.Harmony" version="2.2.0" targetFramework="net472" />
</packages>

File diff suppressed because it is too large Load diff

View file

@ -1,25 +0,0 @@
using System.Text.RegularExpressions;
using Mono.Cecil;
Console.WriteLine("Starting assembly editing...");
var fileRegex =
new Regex(".*(Techblox|Gamecraft|RobocraftX|FullGame|RobocraftECS|DataLoader|RCX|GameState|Svelto\\.ECS)[^/]*(\\.dll)");
foreach (var file in Directory.EnumerateFiles(@"../../../../../ref/Techblox_Data/Managed"))
{
if (!fileRegex.IsMatch(file)) continue;
Console.WriteLine(file);
ProcessAssembly(file);
}
void ProcessAssembly(string path)
{
using var mod = ModuleDefinition.ReadModule(path, new(ReadingMode.Immediate) { ReadWrite = true });
foreach (var typeDefinition in mod.Types)
{
typeDefinition.IsPublic = true;
foreach (var method in typeDefinition.Methods) method.IsPublic = true;
foreach (var field in typeDefinition.Fields) field.IsPublic = true;
}
mod.Write();
}

View file

@ -5,10 +5,6 @@ VisualStudioVersion = 16.0.29411.108
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechbloxModdingAPI", "TechbloxModdingAPI\TechbloxModdingAPI.csproj", "{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGenerator", "CodeGenerator\CodeGenerator.csproj", "{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MakeEverythingPublicInGame", "MakeEverythingPublicInGame\MakeEverythingPublicInGame.csproj", "{391A3107-E5C6-4A04-9467-6D868AA9A8B4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -22,18 +18,6 @@ Global
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Release|Any CPU.Build.0 = Release|Any CPU
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Test|Any CPU.ActiveCfg = Test|Any CPU
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Test|Any CPU.Build.0 = Test|Any CPU
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Release|Any CPU.Build.0 = Release|Any CPU
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Test|Any CPU.ActiveCfg = Debug|Any CPU
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Test|Any CPU.Build.0 = Debug|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Release|Any CPU.Build.0 = Release|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Test|Any CPU.ActiveCfg = Debug|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Test|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -1,58 +0,0 @@
using System;
using System.Collections.Generic;
using HarmonyLib;
using Svelto.Tasks;
using Techblox.Anticheat.Client;
namespace TechbloxModdingAPI.App
{
public static class AntiAntiCheatPatch
{
private delegate bool AntiAnticheatDelegate(ref object __result);
private delegate bool AntiAnticheatDelegateBool(ref bool __result);
private delegate bool AntiAnticheatDelegateTask(ref IEnumerator<TaskContract> __result);
public static void Init(Harmony harmony)
{
var type = AccessTools.TypeByName("Techblox.Services.Eos.Anticheat.Client.Services.AnticheatClientService");
harmony.Patch(type.GetConstructors()[0], new HarmonyMethod(((Func<bool>) AntiAntiCheat).Method));
harmony.Patch(AccessTools.Method(type, "Shutdown"), new HarmonyMethod(((Func<bool>) AntiAntiCheat).Method));
harmony.Patch(AccessTools.Method(type, "StartProtectedSession"), new HarmonyMethod(((AntiAnticheatDelegate) AntiAntiCheat).Method));
harmony.Patch(AccessTools.Method(type, "StopProtectedSession"), new HarmonyMethod(((AntiAnticheatDelegateBool) AntiAntiCheat).Method));
harmony.Patch(AccessTools.Method("Techblox.Services.Eos.Anticheat.Client.EosGetPendingMessagesToSendServiceRequest:Execute"), new HarmonyMethod(((AntiAnticheatDelegateTask)AntiAntiCheatTask).Method));
harmony.Patch(AccessTools.Method("Techblox.Anticheat.Client.Engines.ProcessEACViolationEngine:PollAnticheatStatus"), new HarmonyMethod(((AntiAnticheatDelegateTask)AntiAntiCheatTask).Method));
harmony.Patch(AccessTools.Method(typeof(AnticheatClientCompositionRoot), "ClientComposeTimeRunning"), new HarmonyMethod(((Func<bool>)AntiAntiCheat).Method));
}
private static bool AntiAntiCheat() => false;
private static bool AntiAntiCheat(ref object __result)
{
var targetType =
AccessTools.TypeByName("Techblox.Services.Eos.Anticheat.Client.Services.StartProtectedSessionResult");
var target = Activator.CreateInstance(targetType);
targetType.GetField("Success").SetValue(target, true);
__result = target;
return false;
}
private static bool AntiAntiCheat(ref bool __result)
{
__result = true;
return false;
}
private static bool AntiAntiCheatTask(ref IEnumerator<TaskContract> __result)
{
IEnumerator<TaskContract> Func()
{
yield return Yield.It;
}
__result = Func();
return false;
}
}
}

View file

@ -0,0 +1,63 @@
using System;
using RobocraftX.GUI.MyGamesScreen;
using RobocraftX.GUI;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App
{
public class AppEngine : IFactoryEngine
{
public event EventHandler<MenuEventArgs> EnterMenu;
public event EventHandler<MenuEventArgs> ExitMenu;
public IEntityFactory Factory { set; private get; }
public string Name => "TechbloxModdingAPIAppEngine";
public bool isRemovable => false;
public EntitiesDB entitiesDB { set; private get; }
public void Dispose()
{
IsInMenu = false;
ExceptionUtil.InvokeEvent(ExitMenu, this, new MenuEventArgs { });
}
public void Ready()
{
IsInMenu = true;
ExceptionUtil.InvokeEvent(EnterMenu, this, new MenuEventArgs { });
}
// app functionality
public bool IsInMenu
{
get;
private set;
} = false;
public Game[] GetMyGames()
{
EntityCollection<MyGameDataEntityStruct> mgsevs = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
var mgsevsB = mgsevs.ToBuffer().buffer;
Game[] games = new Game[mgsevs.count];
for (int i = 0; i < mgsevs.count; i++)
{
Utility.Logging.MetaDebugLog($"Found game named {mgsevsB[i].GameName}");
games[i] = new Game(mgsevsB[i].ID);
}
return games;
}
}
public struct MenuEventArgs
{
}
}

View file

@ -14,19 +14,22 @@ namespace TechbloxModdingAPI.App
/// </summary>
public class Client
{
public static Client Instance { get; } = new Client();
// extensible engine
protected static AppEngine appEngine = new AppEngine();
protected static Func<object> ErrorHandlerInstanceGetter;
protected static Action<object, Error> EnqueueError;
protected static Action<object> HandleErrorClosed;
/// <summary>
/// An event that fires whenever the main menu is loaded.
/// </summary>
public static event EventHandler<MenuEventArgs> EnterMenu
{
add => Game.menuEngine.EnterMenu += value;
remove => Game.menuEngine.EnterMenu -= value;
add => appEngine.EnterMenu += ExceptionUtil.WrapHandler(value);
remove => appEngine.EnterMenu -= value;
}
/// <summary>
@ -34,8 +37,8 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<MenuEventArgs> ExitMenu
{
add => Game.menuEngine.ExitMenu += value;
remove => Game.menuEngine.ExitMenu -= value;
add => appEngine.ExitMenu += ExceptionUtil.WrapHandler(value);
remove => appEngine.ExitMenu -= value;
}
/// <summary>
@ -66,8 +69,8 @@ namespace TechbloxModdingAPI.App
{
get
{
if (!Game.menuEngine.IsInMenu) return Array.Empty<Game>();
return Game.menuEngine.GetMyGames();
if (!appEngine.IsInMenu) return new Game[0];
return appEngine.GetMyGames();
}
}
@ -77,7 +80,7 @@ namespace TechbloxModdingAPI.App
/// <value><c>true</c> if in menu; <c>false</c> when loading or in a game.</value>
public bool InMenu
{
get => Game.menuEngine.IsInMenu;
get => appEngine.IsInMenu;
}
/// <summary>
@ -93,26 +96,14 @@ namespace TechbloxModdingAPI.App
EnqueueError(errorHandlerInstance, popup);
}
public void CloseCurrentPrompt()
// TODO
/*public void CloseCurrentPrompt()
{
// RobocraftX.Services.ErrorHandler.Instance.HandlePopupClosed();
// FIXME: this is a call that is also called when closing, not the actual closing action itself (so it doesn't work)
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.Close();
}
public void SelectFirstPromptButton()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.FirstButton();
}
public void SelectSecondPromptButton()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.SecondButton();
}
HandleErrorClosed(errorHandlerInstance);
}*/
internal static void Init()
{
@ -125,6 +116,11 @@ namespace TechbloxModdingAPI.App
EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError")
.MakeGenericMethod(errorHandler, errorHandle)
.Invoke(null, new object[0]);
/*HandleErrorClosed = (Action<object>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenHandlePopupClosed")
.MakeGenericMethod(errorHandler)
.Invoke(null, new object[0]);*/
// register engines
MenuEngineManager.AddMenuEngine(appEngine);
}
// Creating delegates once is faster than reflection every time
@ -149,23 +145,14 @@ namespace TechbloxModdingAPI.App
return enqueueCasted;
}
private static (Action Close, Action FirstButton, Action SecondButton) _errorPopup;
private static (Action Close, Action FirstButton, Action SecondButton) GetPopupCloseMethods(object handler)
private static Action<object> GenHandlePopupClosed<T>()
{
if (_errorPopup.Close != null)
return _errorPopup;
Type errorHandler = handler.GetType();
FieldInfo field = AccessTools.Field(errorHandler, "errorPopup");
var errorPopup = (ErrorPopup)field.GetValue(handler);
MethodInfo info = AccessTools.Method(errorPopup.GetType(), "ClosePopup");
var close = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleFirstOption");
var first = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleSecondOption");
var second = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
_errorPopup = (close, first, second);
return _errorPopup;
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
MethodInfo handlePopupClosed = AccessTools.Method(errorHandler, "HandlePopupClosed");
Action<T> handleSimple =
(Action<T>) Delegate.CreateDelegate(typeof(Action<T>), handlePopupClosed);
Action<object> handleCasted = (object instance) => handleSimple((T) instance);
return handleCasted;
}
}
}

View file

@ -42,25 +42,17 @@ namespace TechbloxModdingAPI.App
[APITestCase(TestType.Menu)]
public static void TestPopUp2()
{
Client.Instance.PromptUser(popup2);
Client c = new Client();
c.PromptUser(popup2);
//c.CloseCurrentPrompt();
}
[APITestCase(TestType.Menu)]
public static void TestPopUp1()
{
Client.Instance.PromptUser(popup1);
}
[APITestCase(TestType.Menu)]
public static void TestPopUpClose1()
{
Client.Instance.CloseCurrentPrompt();
}
[APITestCase(TestType.Menu)]
public static void TestPopUpClose2()
{
Client.Instance.CloseCurrentPrompt();
Client c = new Client();
c.PromptUser(popup1);
//c.CloseCurrentPrompt();
}
}
#endif

View file

@ -1,27 +1,23 @@
using System;
namespace TechbloxModdingAPI.App
namespace TechbloxModdingAPI.App
{
public enum CurrentGameMode
{
None,
/// <summary>
/// Building a world
/// Building a game
/// </summary>
Build,
/// <summary>
/// Playing on a map
/// Playing a game
/// </summary>
Play,
/// <summary>
/// Viewing a prefab (doesn't exist anymore)
/// Viewing a prefab
/// </summary>
[Obsolete]
View,
/// <summary>
/// Viewing a tutorial (doesn't exist anymore)
/// Viewing a tutorial
/// </summary>
[Obsolete]
Tutorial
}
}

View file

@ -2,10 +2,12 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using RobocraftX.Common;
using RobocraftX.GUI.MyGamesScreen;
using RobocraftX.StateSync;
using Svelto.ECS;
using Techblox.GameSelection;
using TechbloxModdingAPI;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility;
@ -21,7 +23,7 @@ namespace TechbloxModdingAPI.App
{
// extensible engines
protected static GameGameEngine gameEngine = new GameGameEngine();
protected internal static GameMenuEngine menuEngine = new GameMenuEngine();
protected static GameMenuEngine menuEngine = new GameMenuEngine();
protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine();
protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine();
@ -91,7 +93,7 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<GameEventArgs> Simulate
{
add => buildSimEventEngine.SimulationMode += value;
add => buildSimEventEngine.SimulationMode += ExceptionUtil.WrapHandler(value);
remove => buildSimEventEngine.SimulationMode -= value;
}
@ -101,7 +103,7 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<GameEventArgs> Edit
{
add => buildSimEventEngine.BuildMode += value;
add => buildSimEventEngine.BuildMode += ExceptionUtil.WrapHandler(value);
remove => buildSimEventEngine.BuildMode -= value;
}
@ -110,7 +112,7 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<GameEventArgs> Enter
{
add => gameEngine.EnterGame += value;
add => gameEngine.EnterGame += ExceptionUtil.WrapHandler(value);
remove => gameEngine.EnterGame -= value;
}
@ -120,7 +122,7 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<GameEventArgs> Exit
{
add => gameEngine.ExitGame += value;
add => gameEngine.ExitGame += ExceptionUtil.WrapHandler(value);
remove => gameEngine.ExitGame -= value;
}
@ -163,7 +165,7 @@ namespace TechbloxModdingAPI.App
{
if (!VerifyMode()) return null;
if (menuMode) return menuEngine.GetGameInfo(EGID).GameName;
return gameEngine.GetGameData().saveName;
return GameMode.SaveGameDetails.Name;
}
set
@ -172,7 +174,11 @@ namespace TechbloxModdingAPI.App
if (menuMode)
{
menuEngine.SetGameName(EGID, value);
} // Save details are directly saved from user input or not changed at all when in game
}
else
{
GameMode.SaveGameDetails.Name = value;
}
}
}
@ -195,7 +201,11 @@ namespace TechbloxModdingAPI.App
if (menuMode)
{
menuEngine.SetGameDescription(EGID, value);
} // No description exists in-game
}
else
{
// No description exists in-game
}
}
}
@ -209,7 +219,7 @@ namespace TechbloxModdingAPI.App
{
if (!VerifyMode()) return null;
if (menuMode) return menuEngine.GetGameInfo(EGID).SavedGamePath;
return gameEngine.GetGameData().gameID;
return GameMode.SaveGameDetails.Folder;
}
set
@ -219,6 +229,11 @@ namespace TechbloxModdingAPI.App
{
menuEngine.GetGameInfo(EGID).SavedGamePath.Set(value);
}
else
{
// this likely breaks things
GameMode.SaveGameDetails = new SaveGameDetails(GameMode.SaveGameDetails.Name, value, GameMode.SaveGameDetails.WorkshopId);
}
}
}
@ -227,16 +242,28 @@ namespace TechbloxModdingAPI.App
/// In most cases this is invalid and returns 0, so this can be ignored.
/// </summary>
/// <value>The workshop identifier.</value>
[Obsolete]
public ulong WorkshopId
{
get
{
return 0uL; // Not supported anymore
if (!VerifyMode()) return 0uL;
if (menuMode) return 0uL; // MyGames don't have workshop IDs
return GameMode.SaveGameDetails.WorkshopId;
}
set
{
VerifyMode();
if (menuMode)
{
// MyGames don't have workshop IDs
// menuEngine.GetGameInfo(EGID).GameName.Set(value);
}
else
{
// this likely breaks things
GameMode.SaveGameDetails = new SaveGameDetails(GameMode.SaveGameDetails.Name, GameMode.SaveGameDetails.Folder, value);
}
}
}
@ -316,7 +343,7 @@ namespace TechbloxModdingAPI.App
get
{
if (menuMode || !VerifyMode()) return CurrentGameMode.None;
return gameEngine.GetGameData().gameMode == GameMode.CreateWorld ? CurrentGameMode.Build : CurrentGameMode.Play;
return (CurrentGameMode) GameMode.CurrentMode;
}
}
@ -446,15 +473,6 @@ namespace TechbloxModdingAPI.App
return blocks;
}
/// <summary>
/// Enable the screenshot taker for updating the game's screenshot. Breaks the pause menu in a new save.
/// </summary>
public void EnableScreenshotTaker()
{
if (!VerifyMode()) return;
gameEngine.EnableScreenshotTaker();
}
~Game()
{
foreach (string id in debugIds)

View file

@ -11,9 +11,9 @@ namespace TechbloxModdingAPI.App
{
public class GameBuildSimEventEngine : IApiEngine, IUnorderedInitializeOnTimeRunningModeEntered, IUnorderedInitializeOnTimeStoppedModeEntered
{
public WrappedHandler<GameEventArgs> SimulationMode;
public event EventHandler<GameEventArgs> SimulationMode;
public WrappedHandler<GameEventArgs> BuildMode;
public event EventHandler<GameEventArgs> BuildMode;
public string Name => "TechbloxModdingAPIBuildSimEventGameEngine";
@ -27,13 +27,13 @@ namespace TechbloxModdingAPI.App
public JobHandle OnInitializeTimeRunningMode(JobHandle inputDeps)
{
SimulationMode.Invoke(this, new GameEventArgs { GameName = "", GamePath = "" }); // TODO
ExceptionUtil.InvokeEvent(SimulationMode, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
return inputDeps;
}
public JobHandle OnInitializeTimeStoppedMode(JobHandle inputDeps)
{
BuildMode.Invoke(this, new GameEventArgs { GameName = "", GamePath = "" });
ExceptionUtil.InvokeEvent(BuildMode, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
return inputDeps;
}
}

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using HarmonyLib;
using RobocraftX;
using RobocraftX.Common;
using RobocraftX.Schedulers;
@ -9,25 +10,17 @@ using Svelto.ECS;
using Svelto.Tasks;
using Svelto.Tasks.Lean;
using RobocraftX.Blocks;
using RobocraftX.Common.Loading;
using RobocraftX.Multiplayer;
using RobocraftX.ScreenshotTaker;
using Techblox.Environment.Transition;
using Techblox.GameSelection;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App
{
public class GameGameEngine : IApiEngine, IReactOnAddAndRemove<LoadingActionEntityStruct>
public class GameGameEngine : IApiEngine
{
public WrappedHandler<GameEventArgs> EnterGame;
public event EventHandler<GameEventArgs> EnterGame;
public WrappedHandler<GameEventArgs> ExitGame;
public event EventHandler<GameEventArgs> ExitGame;
public string Name => "TechbloxModdingAPIGameInfoMenuEngine";
@ -35,35 +28,16 @@ namespace TechbloxModdingAPI.App
public EntitiesDB entitiesDB { set; private get; }
private bool enteredGame;
private bool loadingFinished;
private bool playerJoined;
public void Dispose()
{
if (GameReloadedPatch.IsReload)
return; // Toggling time mode
ExitGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID });
ExceptionUtil.InvokeEvent(ExitGame, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
IsInGame = false;
loadingFinished = false;
playerJoined = false;
enteredGame = false;
}
public void Ready()
{
if (GameReloadedPatch.IsReload)
return; // Toggling time mode
enteredGame = true;
Player.Joined += OnPlayerJoined;
}
private void OnPlayerJoined(object sender, PlayerEventArgs args)
{
if (args.Player.Type != PlayerType.Local) return;
playerJoined = true;
Player.Joined -= OnPlayerJoined;
CheckJoinEvent();
ExceptionUtil.InvokeEvent(EnterGame, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
IsInGame = true;
}
// game functionality
@ -78,7 +52,7 @@ namespace TechbloxModdingAPI.App
{
if (async)
{
ExitCurrentGameAsync().RunOn(ClientLean.EveryFrameStepRunner_TimeRunningAndStopped);
ExitCurrentGameAsync().RunOn(Lean.EveryFrameStepRunner_TimeRunningAndStopped);
}
else
{
@ -118,78 +92,40 @@ namespace TechbloxModdingAPI.App
public void ToggleTimeMode()
{
if (TimeRunningModeUtil.IsTimeStoppedMode(entitiesDB))
FakeInput.ActionInput(toggleMode: true);
else
{
IEnumerator<TaskContract> ReloadBuildModeTask()
{
SwitchAnimationUtil.Start(entitiesDB);
while (SwitchAnimationUtil.IsFadeOutActive(entitiesDB))
yield return (TaskContract)Yield.It;
FullGameFields._multiplayerParams.MultiplayerMode = MultiplayerMode.SinglePlayer;
AccessTools.Method(typeof(FullGameCompositionRoot), "ReloadGame")
.Invoke(FullGameFields.Instance, new object[] { });
}
ReloadBuildModeTask().RunOn(ClientLean.UIScheduler);
}
TimeRunningModeUtil.ToggleTimeRunningState(entitiesDB);
}
public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid)
{
var allBlocks = entitiesDB.QueryEntities<BlockTagEntityStruct>();
List<EGID> blockEGIDs = new List<EGID>();
foreach (var ((_, ids, count), group) in allBlocks)
{
for (int i = 0; i < count; i++)
{
var id = new EGID(ids[i], group);
uint dbid;
if (filter == BlockIDs.Invalid)
dbid = (uint)filter;
{
foreach (var (blocks, _) in allBlocks)
{
var buffer = blocks.ToBuffer().buffer;
for (int i = 0; i < buffer.capacity; i++)
blockEGIDs.Add(buffer[i].ID);
}
return blockEGIDs.ToArray();
}
else
dbid = entitiesDB.QueryEntity<DBEntityStruct>(id).DBID;
var ownership = entitiesDB.QueryEntity<BlockOwnershipComponent>(id).BlockOwnership;
if ((ownership & BlockOwnership.User) != 0 && dbid == (ulong)filter)
blockEGIDs.Add(id);
{
foreach (var (blocks, _) in allBlocks)
{
var array = blocks.ToBuffer().buffer;
for (var index = 0; index < array.capacity; index++)
{
var block = array[index];
uint dbid = entitiesDB.QueryEntity<DBEntityStruct>(block.ID).DBID;
if (dbid == (ulong) filter)
blockEGIDs.Add(block.ID);
}
}
return blockEGIDs.ToArray();
}
public void EnableScreenshotTaker()
{
ref var local = ref entitiesDB.QueryEntity<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker);
if (local.enabled)
return;
local.enabled = true;
entitiesDB.PublishEntityChange<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker);
}
public GameSelectionComponent GetGameData()
{
return entitiesDB.QueryEntity<GameSelectionComponent>(GameSelectionConstants.GameSelectionEGID);
}
public void Add(ref LoadingActionEntityStruct entityComponent, EGID egid)
{
}
public void Remove(ref LoadingActionEntityStruct entityComponent, EGID egid)
{ // Finished loading
if (!enteredGame) return;
enteredGame = false;
loadingFinished = true;
CheckJoinEvent();
}
private void CheckJoinEvent()
{
if (!loadingFinished || !playerJoined) return;
EnterGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID });
IsInGame = true;
}
}
}

View file

@ -1,15 +1,13 @@
using System;
using System.Reflection;
using HarmonyLib;
using RobocraftX;
using RobocraftX.Common;
using RobocraftX.GUI;
using RobocraftX.GUI.MyGamesScreen;
using RobocraftX.Multiplayer;
using Svelto.ECS;
using Svelto.ECS.Experimental;
using Techblox.GameSelection;
using Svelto.DataStructures;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
@ -17,9 +15,6 @@ namespace TechbloxModdingAPI.App
{
public class GameMenuEngine : IFactoryEngine
{
public WrappedHandler<MenuEventArgs> EnterMenu;
public WrappedHandler<MenuEventArgs> ExitMenu;
public IEntityFactory Factory { set; private get; }
public string Name => "TechbloxModdingAPIGameInfoGameEngine";
@ -28,42 +23,23 @@ namespace TechbloxModdingAPI.App
public EntitiesDB entitiesDB { set; private get; }
public GameMenuEngine()
{
MenuEnteredEnginePatch.EnteredExitedMenu = () =>
{
if (IsInMenu)
EnterMenu.Invoke(this, new MenuEventArgs { });
else
ExitMenu.Invoke(this, new MenuEventArgs { });
};
}
public void Dispose()
{
IsInMenu = false;
}
public void Ready()
{
MenuEnteredEnginePatch.IsInMenu = true; // At first it uses ActivateMenu(), then GoToMenu() which is patched
MenuEnteredEnginePatch.EnteredExitedMenu();
IsInMenu = true;
}
// game functionality
public bool IsInMenu => MenuEnteredEnginePatch.IsInMenu;
public Game[] GetMyGames()
public bool IsInMenu
{
var (mgsevs, count) = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
Game[] games = new Game[count];
for (int i = 0; i < count; i++)
{
Utility.Logging.MetaDebugLog($"Found game named {mgsevs[i].GameName}");
games[i] = new Game(mgsevs[i].ID);
}
return games;
}
get;
private set;
} = false;
public bool CreateMyGame(EGID id, string path = "", uint thumbnailId = 0, string gameName = "", string creatorName = "", string description = "", long createdDate = 0L)
{
@ -83,13 +59,14 @@ namespace TechbloxModdingAPI.App
public uint HighestID()
{
var (games, count) = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
EntityCollection<MyGameDataEntityStruct> games = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
var gamesB = games.ToBuffer().buffer;
uint max = 0;
for (int i = 0; i < count; i++)
for (int i = 0; i < games.count; i++)
{
if (games[i].ID.entityID > max)
if (gamesB[i].ID.entityID > max)
{
max = games[i].ID.entityID;
max = gamesB[i].ID.entityID;
}
}
return max;
@ -99,19 +76,15 @@ namespace TechbloxModdingAPI.App
{
if (!ExistsGameInfo(id)) return false;
ref MyGameDataEntityStruct mgdes = ref GetGameInfo(id);
return EnterGame(mgdes.GameName, mgdes.FileId);
return EnterGame(mgdes.GameName, mgdes.SavedGamePath);
}
public bool EnterGame(ECSString gameName, string fileId, bool autoEnterSim = false)
public bool EnterGame(string gameName, string path, ulong workshopId = 0uL, bool autoEnterSim = false)
{
FullGameFields._multiplayerParams.MultiplayerMode = MultiplayerMode.SinglePlayer;
ref var selection = ref entitiesDB.QueryEntity<GameSelectionComponent>(GameSelectionConstants.GameSelectionEGID);
selection.userContentID.Set(fileId);
selection.triggerStart = true;
selection.saveType = SaveType.ExistingSave;
selection.saveName = gameName;
selection.gameMode = GameMode.PlayGame;
selection.gameID.Set("GAMEID_Road_Track"); //TODO: Expose to the API
GameMode.CurrentMode = autoEnterSim ? RCXMode.Play : RCXMode.Build;
GameMode.SaveGameDetails = new SaveGameDetails(gameName, path, workshopId);
// the private FullGameCompositionRoot.SwitchToGame() method gets passed to menu items for this reason
AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame").Invoke(FullGameFields.Instance, new object[0]);
return true;
}
@ -157,41 +130,4 @@ namespace TechbloxModdingAPI.App
}
internal class MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal : GenericEntityDescriptor<MyGameDataEntityStruct> { }
[HarmonyPatch]
static class MenuEnteredEnginePatch
{
internal static bool IsInMenu;
internal static Action EnteredExitedMenu;
public static void Postfix()
{
IsInMenu = true;
EnteredExitedMenu();
}
public static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToMenu");
}
}
[HarmonyPatch]
static class MenuExitedEnginePatch
{
public static void Prefix()
{
MenuEnteredEnginePatch.IsInMenu = false;
MenuEnteredEnginePatch.EnteredExitedMenu();
}
public static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame");
}
}
public struct MenuEventArgs
{
}
}

View file

@ -8,10 +8,8 @@ using Svelto.ECS.EntityStructs;
using RobocraftX.Common;
using RobocraftX.Blocks;
using Unity.Mathematics;
using HarmonyLib;
using RobocraftX.PilotSeat;
using RobocraftX.Rendering;
using Techblox.BlockLabelsServer;
using Gamecraft.Blocks.GUI;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Tests;
@ -67,22 +65,16 @@ namespace TechbloxModdingAPI
/// <returns>The block object or null if doesn't exist</returns>
public static Block GetLastPlacedBlock()
{
uint lastBlockID = CommonExclusiveGroups.blockIDGeneratorClient.Peek() - 1;
EGID? egid = BlockEngine.FindBlockEGID(lastBlockID);
EGID? egid = BlockEngine.FindBlockEGID(BlockIdentifiers.LatestBlockID);
return egid.HasValue ? New(egid.Value) : null;
}
/*public static Block CreateGhostBlock()
{
return BlockGroup._engine.BuildGhostChild();
}*/
/// <summary>
/// An event that fires each time a block is placed.
/// </summary>
public static event EventHandler<BlockPlacedRemovedEventArgs> Placed
{ //TODO: Rename and add instance version in 3.0
add => BlockEventsEngine.Placed += value;
{
add => BlockEventsEngine.Placed += ExceptionUtil.WrapHandler(value);
remove => BlockEventsEngine.Placed -= value;
}
@ -91,7 +83,7 @@ namespace TechbloxModdingAPI
/// </summary>
public static event EventHandler<BlockPlacedRemovedEventArgs> Removed
{
add => BlockEventsEngine.Removed += value;
add => BlockEventsEngine.Removed += ExceptionUtil.WrapHandler(value);
remove => BlockEventsEngine.Removed -= value;
}
@ -99,42 +91,19 @@ namespace TechbloxModdingAPI
new Dictionary<ExclusiveBuildGroup, (Func<EGID, Block>, Type)>
{
{CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, (id => new DampedSpring(id), typeof(DampedSpring))},
{CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))},
{CommonExclusiveGroups.LOGIC_BLOCK_GROUP, (id => new LogicGate(id), typeof(LogicGate))},
{CommonExclusiveGroups.PISTON_BLOCK_GROUP, (id => new Piston(id), typeof(Piston))},
{CommonExclusiveGroups.SERVO_BLOCK_GROUP, (id => new Servo(id), typeof(Servo))},
{CommonExclusiveGroups.WHEELRIG_BLOCK_BUILD_GROUP, (id => new WheelRig(id), typeof(WheelRig))}
{CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))}
};
static Block()
internal static Block New(EGID egid)
{
foreach (var group in SeatGroups.SEATS_BLOCK_GROUPS) // Adds driver and passenger seats, occupied and unoccupied
GroupToConstructor.Add(group, (id => new Seat(id), typeof(Seat)));
return GroupToConstructor.ContainsKey(egid.groupID)
? GroupToConstructor[egid.groupID].Constructor(egid)
: new Block(egid);
}
/// <summary>
/// Returns a correctly typed instance of this block. The instances are shared for a specific block.
/// If an instance is no longer referenced a new instance is returned.
/// </summary>
/// <param name="egid">The EGID of the block</param>
/// <param name="signaling">Whether the block is definitely a signaling block</param>
/// <returns></returns>
internal static Block New(EGID egid, bool signaling = false)
{
if (egid == default) return null;
if (GroupToConstructor.ContainsKey(egid.groupID))
{
var (constructor, type) = GroupToConstructor[egid.groupID];
return GetInstance(egid, constructor, type);
}
return signaling
? GetInstance(egid, e => new SignalingBlock(e))
: GetInstance(egid, e => new Block(e));
}
public Block(EGID id) : base(id)
public Block(EGID id)
{
Id = id;
Type expectedType;
if (GroupToConstructor.ContainsKey(id.groupID) &&
!GetType().IsAssignableFrom(expectedType = GroupToConstructor[id.groupID].Type))
@ -161,18 +130,17 @@ namespace TechbloxModdingAPI
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param>
/// <param name="player">The player who placed the block</param>
public Block(BlockIDs type, float3 position, bool autoWire = false, Player player = null)
: base(block =>
{
if (!PlacementEngine.IsInGame || !GameState.IsBuildMode())
throw new BlockException("Blocks can only be placed in build mode.");
var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire);
block.InitData = initializer;
Placed += ((Block)block).OnPlacedInit;
return initializer.EGID;
})
{
Id = initializer.EGID;
InitData = initializer;
Placed += OnPlacedInit;
}
public override EGID Id { get; }
private EGID copiedFrom;
/// <summary>
@ -288,7 +256,6 @@ namespace TechbloxModdingAPI
color.indexInPalette = value.Index;
color.hasNetworkChange = true;
color.paletteColour = BlockEngine.ConvertBlockColor(color.indexInPalette); //Setting to 255 results in black
BlockEngine.UpdateBlockColor(Id);
}
}
@ -325,10 +292,7 @@ namespace TechbloxModdingAPI
: throw new BlockTypeException("Unknown block type! Could not set default material.");
if (!FullGameFields._dataDb.ContainsKey<MaterialPropertiesData>(val))
throw new BlockException($"Block material {value} does not exist!");
ref var comp = ref BlockEngine.GetBlockInfo<CubeMaterialStruct>(this);
if (comp.materialId == val)
return;
comp.materialId = val;
BlockEngine.GetBlockInfo<CubeMaterialStruct>(this).materialId = val;
BlockEngine.UpdatePrefab(this, val, Flipped); //The default causes the screen to go black
}
}
@ -340,15 +304,11 @@ namespace TechbloxModdingAPI
[TestValue(null)]
public string Label
{
get
{
var opt = BlockEngine.GetBlockInfoOptional<LabelResourceIDComponent>(this);
return opt ? FullGameFields._managers.blockLabelResourceManager.GetText(opt.Get().instanceID) : null;
}
get => BlockEngine.GetBlockInfoViewComponent<TextLabelEntityViewStruct>(this).textLabelComponent?.text;
set
{
var opt = BlockEngine.GetBlockInfoOptional<LabelResourceIDComponent>(this);
if (opt) FullGameFields._managers.blockLabelResourceManager.SetText(opt.Get().instanceID, value);
var comp = BlockEngine.GetBlockInfoViewComponent<TextLabelEntityViewStruct>(this).textLabelComponent;
if (comp != null) comp.text = value;
}
}
@ -366,12 +326,8 @@ namespace TechbloxModdingAPI
get
{
if (blockGroup != null) return blockGroup;
if (!GameState.IsBuildMode()) return null; // Breaks in simulation
var bgec = BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this);
return blockGroup = bgec.currentBlockGroup == -1
? null
: GetInstance(new EGID((uint)bgec.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup),
egid => new BlockGroup((int)egid.entityID, this));
return blockGroup = bgec.currentBlockGroup == -1 ? null : new BlockGroup(bgec.currentBlockGroup, this);
}
set
{
@ -382,8 +338,6 @@ namespace TechbloxModdingAPI
return;
}
blockGroup?.RemoveInternal(this);
if (!InitData.Valid)
return;
BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this).currentBlockGroup = (int?) value?.Id.entityID ?? -1;
value?.AddInternal(this);
blockGroup = value;
@ -395,25 +349,8 @@ namespace TechbloxModdingAPI
/// </summary>
public bool Static
{
get => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic;
set => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic = value;
}
/// <summary>
/// The mass of the block.
/// </summary>
public float Mass
{
get => BlockEngine.GetBlockInfo<MassStruct>(this).mass;
}
/// <summary>
/// Block complexity used for build rules. Determines the 'cost' of the block.
/// </summary>
public BlockComplexity Complexity
{
get => new(BlockEngine.GetBlockInfo<BlockComplexityComponent>(this));
set => BlockEngine.GetBlockInfo<BlockComplexityComponent>(this) = value;
get => BlockEngine.GetBlockInfo<OverrideStaticComponent>(this).staticIfUnconnected;
set => BlockEngine.GetBlockInfo<OverrideStaticComponent>(this).staticIfUnconnected = value;
}
/// <summary>
@ -441,10 +378,9 @@ namespace TechbloxModdingAPI
public SimBody GetSimBody()
{
var st = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(this);
/*return st.machineRigidBodyId != uint.MaxValue
? new SimBody(st.machineRigidBodyId, st.clusterId) - TODO:
: null;*/
return null;
return st.machineRigidBodyId != uint.MaxValue
? new SimBody(st.machineRigidBodyId, st.clusterId)
: null;
}
/// <summary>
@ -468,7 +404,7 @@ namespace TechbloxModdingAPI
if (e.ID != Id) return;
Placed -= OnPlacedInit; //And we can reference it
InitData = default; //Remove initializer as it's no longer valid - if the block gets removed it shouldn't be used again
if (copiedFrom != default)
if (copiedFrom != EGID.Empty)
BlockCloneEngine.CopyBlockStats(copiedFrom, Id);
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
@ -19,16 +19,17 @@ namespace TechbloxModdingAPI
public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable
{
internal static BlueprintEngine _engine = new BlueprintEngine();
public override EGID Id { get; }
private readonly Block sourceBlock;
private readonly List<Block> blocks;
private float3 position, rotation;
internal bool PosAndRotCalculated;
internal BlockGroup(int id, Block block) : base(new EGID((uint)id,
BlockGroupExclusiveGroups.BlockGroupEntityGroup))
internal BlockGroup(int id, Block block)
{
if (id == BlockGroupUtility.GROUP_UNASSIGNED)
throw new BlockException("Cannot create a block group for blocks without a group!");
Id = new EGID((uint) id, BlockGroupExclusiveGroups.BlockGroupEntityGroup);
sourceBlock = block;
blocks = new List<Block>(GetBlocks());
Block.Removed += OnBlockRemoved;

View file

@ -1,17 +0,0 @@
using RobocraftX.Blocks;
namespace TechbloxModdingAPI.Blocks
{
public record BlockComplexity(int Cpu, int Power)
{
public BlockComplexity(BlockComplexityComponent component) : this(component.cpu, component.power)
{
}
public int Cpu { get; } = Cpu;
public int Power { get; } = Power;
public static implicit operator BlockComplexityComponent(BlockComplexity complexity) =>
new() { cpu = complexity.Cpu, power = complexity.Power };
}
}

View file

@ -25,7 +25,38 @@ namespace TechbloxModdingAPI.Blocks
PlateQuarterPyramid,
PlateTetrahedron,
Sphere,
CarWheelArch = 47,
Frame,
FrameS1,
FrameS2,
FrameS3,
FrameS4,
FrameS5,
FrameWedge,
FrameWedgeS1,
FrameWedgeS2,
FrameWedgeS3,
FrameWedgeS4,
SideS0S1,
SideS0S2,
SideS0S3,
SideS0S4,
SideS0S5,
SideS1S1,
SideS1S2,
SideS1S3,
SideS1S4,
SideS1S5,
SideS2S1,
SideS2S2,
SideS2S3,
SideS2S4,
SideS2S5,
WindscreenS1,
WindscreenS2,
WindscreenS3,
WindscreenS4,
WindscreenS5,
CarWheelArch,
CarArchSmallFlare,
CarArchFlare,
CarArchExtrudedFlare,
@ -42,8 +73,8 @@ namespace TechbloxModdingAPI.Blocks
PlateTriangle = 130,
PlateCircle,
PlateQuarterCircle,
PlateRoundedWedge,
PlateRoundedTetrahedron,
PlateRWedge,
PlateRTetrahedron,
Cone,
ConeSegment,
DoubleSliced,
@ -75,309 +106,7 @@ namespace TechbloxModdingAPI.Blocks
WideCylinderDiagonal,
NarrowCylinderDiagonal,
HeadlampTetrahedron,
GoKartEngine,
Screen5X2Y2Z,
Screen5X2Y3Z,
Screen5X2Y5Z,
Screen9X2Y2Z,
Screen9X3Y2Z,
Screen9X2Y3Z,
Screen9X3Y3Z,
Screen9X2Y5Z,
Screen9X3Y5Z,
Screen11X3Y2Z,
Screen11X3Y3Z,
Screen11X3Y5Z,
Window6X2Y2Z,
Window6X3Y2Z,
Window6X2Y2ZS1,
Window6X3Y2ZS1,
Window6X2Y2ZS2,
Window6X3Y2ZS2,
Window6X2Y2ZS4,
Window6X3Y2ZS4,
FrameSquare,
FrameSkewedSquare,
FrameTriangle,
FrameSkewedTriangle,
GlassFrameSquare,
GlassFrameSkewedSquare,
GlassFrameTriangle,
GlassFrameSkewedTriangle,
GlassPlate,
GlassPlateTriangle,
GoKartWheelRigNoSteering,
GoKartWheelRigWithSteering,
GoKartSeat,
CarWheelWideProfile,
CarWheelWideProfile = 200,
CarWheel,
GoKartWheelWideProfile,
GoKartWheel,
ANDLogicGate,
ORLogicGate,
NOTLogicGate,
NANDLogicGate,
NORLogicGate,
XORLogicGate,
XNORLogicGate,
AdderMathBlock,
SubtractorMathBlock,
MultiplierMathBlock,
DividerMathBlock,
InverterMathBlock,
AverageMathBlock,
AbsoluteMathBlock,
MinMathBlock,
MaxMathBlock,
SimpleConnector,
Motor,
AxleServo,
HingeServo,
Piston,
Button,
Switch,
Dial,
Lever,
ThreeWaySwitch,
EqualsMathBlock,
LessThanMathBlock,
LessThanOrEqualMathBlock,
GreaterThanMathBlock,
GreaterThanOrEqualMathBlock,
HatchbackWheelRigNoSteering,
HatchbackWheelRigWithSteering,
HatchbackEngine,
HatchbackWheel,
HatchbackWheelArch,
HatchbackArchSmallFlare,
HatchbackArchFlare,
CeilingStripLight,
CardboardBox,
BarrierRail,
BarrierRailEnd,
TruckWheel,
HatchbackWheelWideProfile,
TruckWheelRigWithSteering = 249,
TruckWheelRigNoSteering,
HatchbackDriverSeat,
HatchbackPassengerSeat,
FormulaEngine,
SmallGrass,
SmallGrassRoad,
GrassBridge,
SmallGrassTurn,
MediumGrassTurn,
LargeGrassTurn,
ExtraLargeGrassTurn,
TruckWheelDouble,
TruckWheelArch,
TruckArchSingleFlare,
WoodenDoorWithWindow,
TyreBarrierCorner,
TyreBarrierEdge,
TyreBarrierCenter,
AppleTree,
AppleForestTree,
FormulaWheel,
FormulaWheelRear,
AppleSapling,
GrassHill,
GrassHillInnerCorner,
GrassHillOuterCorner,
GrassRoadHill,
FormulaSeat,
SmallDirt,
SmallDirtRoad,
SmallDirtTurn,
MediumDirtTurn,
LargeDirtTurn,
ExtraLargeDirtTurn,
SmallGrid,
MonsterTruckWheel,
SmallGrassGridStart,
SmallGrassRumbleStripRoad,
SmallGrassRumbleStripEndRoad,
SmallGrassStartLine,
MonsterTruckEngine,
DirtHill,
DirtHillInnerCorner,
DirtHillOuterCorner,
BuildingWindowEdge,
BuildingWindowCorner,
BuildingWindowStraight,
BuildingWindowTJunction,
BuildingWindowCross,
BuildingWindowEdgeSill,
BuildingWindowCornerSill,
BuildingWindowTJunctionSill,
Broadleaf,
ForestBroadleaf,
AzaleaBush,
AzaleaFlowers1,
AzaleaFlowers2,
TreeStump1,
TreeStump2,
FieldJuniper,
ForestJuniper,
JuniperSapling,
JuniperSeedling,
FieldRedMaple,
RedMapleForest1,
RedMapleForest2,
RedMapleSapling,
FieldWhiteSpruce,
ForestWhiteSpruce,
WhiteSpruceSapling,
GirderBase,
GirderStraight,
GirderDiagonal,
GirderCorner,
PostBase,
PostStraight,
PostLShape,
PostTJunction,
PostCross,
PostCorner,
PostDiagonal,
DirtRock1,
DirtRock2,
DirtRock3,
DirtRock4,
DirtRoadHill,
WoodenPalette,
ElderberryBush,
BarrelCactus,
KnapweedFlower,
MarigoldFlowers,
TrampledBushyBluestep,
RoughGrass,
DogRose,
WesternSwordFern,
BackyardGrass,
ThickGrass,
FireExtinguisher,
DirtLowRamp,
DirtTabletopRamp,
MonsterTruckWheelRigNoSteering,
MonsterTruckWheelRigWithSteering,
MeadowCloudyDayAtmosphere,
BarrierRailDiagonal,
DirtHighRamp,
GrassRock1,
GrassRock2,
GrassRock3,
GrassRock4,
GreenFieldsSunnyDayAtmosphere,
RedMountainsDawnAtmosphere,
HighFantasySunriseAtmosphere,
/// <summary>
/// The grid block used by the world editor, named Small Grid like the other one
/// </summary>
SmallGridInWorldEditor,
CityDoubleCrossing,
CityDoubleCrossroads,
CitySmallDoubleJunction,
CityDoubleJunction,
CityDoubleToSingleJunction,
CitySmallDoubleRoad,
CityDoubleRoad,
CitySmallDoubleTurn,
CityLargeDoubleTurn,
CitySmallSingleTurn,
CityLargeSingleTurn,
CitySingleJunction,
CitySingleRoad,
SegoeUITextblock,
GravtracTextblock,
HauserTextblock,
TechnopollasTextblock,
CityDoubleHillRoad,
DiagonalTrackTile,
DiagonalTrackTile2,
DiagonalTransitionTile,
SplitLane,
BitBlock,
Timer,
CityNightAtmosphere,
FloodLight,
SoccerBall,
CircularWallLight,
BlueSkyAtmos,
DirtToGrassTransitionTile = 393,
DirtToGrassTransitionInnerTile,
DirtToGrassTransitionOuterTile,
DirtToGrassTransitionHillTile,
DirtToGrassTransitionRoadTile,
DirtHill2 = 399,
DirtHill3,
DirtInnerCorner2 = 402,
DirtInnerCorner3,
DirtOuterCorner2 = 405,
DirtOuterCorner3,
CityTarmacEdgeInner,
CityTarmacEdgeOuter,
CityTarmacEdgeRoad,
CityTarmac,
SmallGrassQuarterTile,
CityToRacetrackTransition,
HUDTimer,
CentreHUD,
Checkpoint,
ScoreboardHUD,
GameplaySFX,
SpawnPoint,
AreaSensor,
WorldResetter,
SmallJet,
MediumJet,
LargeJet,
DistanceSensor,
Stabilizer,
ObjectID,
ScoreToTechpointConversion,
TeamScore,
ScorePickupBlock,
SportyHatchbackDriverSeat,
SportyHatchbackPassengerSeat,
FlamingExhaust = 433,
SmokingExhaust,
StreetLamp,
Vector7HatchbackWheel,
Vector7HatchbackWheelWideProfile,
Vector7SedanWheel,
Vector7SedanWideProfile,
Vector7FormulaWheel,
Vector7FormulaWheelRear,
Vector7MonsterTruckWheel,
Vector7TruckWheel,
Vector7TruckWheelDouble,
BusSeat,
XLJet,
XXLJet,
ElectricSedanEngine,
HeadlampIndicator,
HeadlampSrip,
HeadlampStripEdge,
ConstantBlock,
CounterBlock,
SmallGridHill,
SmallGridHillInnerCorner,
SmallGridHillOuterCorner,
AimingAxleServo,
AimingHingeServo,
WeaponDisabler,
Vector7SmallJet,
Vector7MediumJet,
Vector7LargeJet,
Vector7XLJet,
Vector7XXLJet,
APCWheelRigNoSteering,
APCWheelRigWithSteering,
APCWheel,
APCSeat,
APCEngine,
DamageScoreBlock,
KillScoreBlock,
Autocannon = 480
}
}

View file

@ -0,0 +1,43 @@
using Svelto.ECS;
using RobocraftX.Common;
using HarmonyLib;
using Svelto.DataStructures;
namespace TechbloxModdingAPI.Blocks
{
/// <summary>
/// ExclusiveGroups and IDs used with blocks
/// </summary>
public static class BlockIdentifiers
{
/// <summary>
/// Blocks placed by the player
/// </summary>
public static FasterReadOnlyList<ExclusiveGroupStruct> OWNED_BLOCKS { get { return CommonExclusiveGroups.REAL_BLOCKS_GROUPS_DON_T_USE_IN_NEW_CODE; } }
/// <summary>
/// Extra parts used in functional blocks
/// </summary>
public static ExclusiveGroup FUNCTIONAL_BLOCK_PARTS { get { return CommonExclusiveGroups.FUNCTIONAL_BLOCK_PART_GROUP; } }
/// <summary>
/// Blocks which are disabled in Simulation mode
/// </summary>
public static ExclusiveGroup SIM_BLOCKS_DISABLED { get { return CommonExclusiveGroups.DISABLED_JOINTS_IN_SIM_GROUP; } }
//public static ExclusiveGroup SPAWN_POINTS { get { return CommonExclusiveGroups.SPAWN_POINTS_GROUP; } }
//public static ExclusiveGroup SPAWN_POINTS_DISABLED { get { return CommonExclusiveGroups.SPAWN_POINTS_DISABLED_GROUP; } }
/// <summary>
/// The ID of the most recently placed block
/// </summary>
public static uint LatestBlockID {
get
{ //Need the private field as the property increments itself
return ((uint) AccessTools.Field(typeof(CommonExclusiveGroups), "_nextBlockEntityID").GetValue(null)) - 1;
}
}
}
}

View file

@ -7,40 +7,6 @@ namespace TechbloxModdingAPI.Blocks
RigidSteel,
CarbonFiber,
Plastic,
Wood = 6,
RigidSteelPainted,
RigidSteelRustedPaint,
RigidSteelHeavyRust,
SteelBodyworkMetallicPaint,
SteelBodyworkRustedPaint,
SteelBodyworkHeavyRust,
WoodVarnishedDark,
Chrome,
FenceChainLink,
ConcreteUnpainted,
Grid9x9,
CeramicTileFloor,
PlasticBumpy,
PlasticDustySmeared,
AluminiumGarageDoor,
SteelRigidScratched,
AluminiumBrushedTinted,
AluminiumSheetStained,
ConcretePaintedGrooves,
PlasticSpecklySatin,
SteelBodyworkPaintedChipped,
WoodPainted,
WoodRoughGrungy,
Boundary,
Emissive,
AircraftPanelingRivetedPainted,
AircraftPanelingRivetedMetallic,
SteelBodyworkPearlescent,
SteelBodyworkRadWrap,
SteelBodyworkGlitter,
BouncyRubber,
BouncyRubberTieDye,
BrickPainted,
FuturisticPanelingRivetedPainted,
Wood = 6
}
}

View file

@ -7,6 +7,7 @@ using DataLoader;
using Svelto.Tasks;
using Unity.Mathematics;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility;
@ -45,20 +46,16 @@ namespace TechbloxModdingAPI.Blocks
"Block ID enum matches the known block types.");
}
private static Block[] blocks; // Store placed blocks as some blocks are already present as the workshop and the game save
[APITestCase(TestType.EditMode)]
public static void TestBlockIDs()
{
float3 pos = new float3();
var values = Enum.GetValues(typeof(BlockIDs));
blocks = new Block[values.Length - 1]; // Minus the invalid ID
int i = 0;
foreach (BlockIDs id in values)
foreach (BlockIDs id in Enum.GetValues(typeof(BlockIDs)))
{
if (id == BlockIDs.Invalid) continue;
try
{
blocks[i++] = Block.PlaceNew(id, pos);
Block.PlaceNew(id, pos);
pos += 0.2f;
}
catch (Exception e)
@ -74,55 +71,38 @@ namespace TechbloxModdingAPI.Blocks
[APITestCase(TestType.EditMode)]
public static IEnumerator<TaskContract> TestBlockProperties()
{ //Uses the result of the previous test case
yield return Yield.It;
if (blocks is null)
yield break;
var blocks = Game.CurrentGame().GetBlocksInGame();
for (var index = 0; index < blocks.Length; index++)
{
if (index % 50 == 0) yield return Yield.It; //The material or flipped status can only be changed 130 times per submission
var block = blocks[index];
if (!block.Exists) continue;
foreach (var property in block.GetType().GetProperties())
{
//Includes specialised block properties
if (property.SetMethod == null) continue;
bool3 Float3Compare(float3 a, float3 b)
{ // From Unity reference code
return math.abs(b - a) < math.max(
0.000001f * math.max(math.abs(a), math.abs(b)),
float.Epsilon * 8
);
}
bool4 Float4Compare(float4 a, float4 b)
{ // From Unity reference code
return math.abs(b - a) < math.max(
0.000001f * math.max(math.abs(a), math.abs(b)),
float.Epsilon * 8
);
}
var testValues = new (Type, object, Predicate<(object Value, object Default)>)[]
var testValues = new (Type, object, Predicate<object>)[]
{
//(type, default value, predicate or null for equality)
(typeof(long), 3, null),
(typeof(int), 4, null),
(typeof(double), 5.2f, t => Math.Abs((double) t.Value - (double) t.Default) < float.Epsilon),
(typeof(float), 5.2f, t => Math.Abs((float) t.Value - (float) t.Default) < float.Epsilon),
(typeof(bool), true, t => (bool) t.Value),
(typeof(string), "Test", t => (string) t.Value == "Test"), //String equality check
(typeof(float3), (float3) 20, t => math.all(Float3Compare((float3)t.Value, (float3)t.Default))),
(typeof(double), 5.2f, obj => Math.Abs((double) obj - 5.2f) < float.Epsilon),
(typeof(float), 5.2f, obj => Math.Abs((float) obj - 5.2f) < float.Epsilon),
(typeof(bool), true, obj => (bool) obj),
(typeof(string), "Test", obj => (string) obj == "Test"), //String equality check
(typeof(float3), (float3) 2, obj => math.all((float3) obj - 2 < (float3) float.Epsilon)),
(typeof(BlockColor), new BlockColor(BlockColors.Aqua, 2), null),
(typeof(float4), (float4) 5, t => math.all(Float4Compare((float4)t.Value, (float4)t.Default)))
(typeof(float4), (float4) 5, obj => math.all((float4) obj - 5 < (float4) float.Epsilon))
};
var propType = property.PropertyType;
if (!propType.IsValueType) continue;
(object valueToUse, Predicate<(object Value, object Default)> predicateToUse) = (null, null);
(object valueToUse, Predicate<object> predicateToUse) = (null, null);
foreach (var (type, value, predicate) in testValues)
{
if (type.IsAssignableFrom(propType))
{
valueToUse = value;
predicateToUse = predicate ?? (t => Equals(t.Value, t.Default));
predicateToUse = predicate ?? (obj => Equals(obj, value));
break;
}
}
@ -131,7 +111,7 @@ namespace TechbloxModdingAPI.Blocks
{
var values = propType.GetEnumValues();
valueToUse = values.GetValue(values.Length / 2);
predicateToUse = t => Equals(t.Value, t.Default);
predicateToUse = val => Equals(val, valueToUse);
}
if (valueToUse == null)
@ -140,26 +120,10 @@ namespace TechbloxModdingAPI.Blocks
yield break;
}
try
{
property.SetValue(block, valueToUse);
}
catch (Exception e)
{
Assert.Fail($"Failed to set property {block.GetType().Name}.{property.Name} to {valueToUse}\n{e}");
}
object got;
try
{
got = property.GetValue(block);
}
catch (Exception e)
{
Assert.Fail($"Failed to get property {block.GetType().Name}.{property.Name}\n{e}");
continue;
}
object got = property.GetValue(block);
var attr = property.GetCustomAttribute<TestValueAttribute>();
if (!predicateToUse((got, valueToUse)) && (attr == null || !Equals(attr.PossibleValue, got)))
if (!predicateToUse(got) && (attr == null || !Equals(attr.PossibleValue, got)))
{
Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}.");
yield break;
@ -170,33 +134,14 @@ namespace TechbloxModdingAPI.Blocks
Assert.Pass("Setting all possible properties of all registered API block types succeeded.");
}
[APITestCase(TestType.EditMode)]
public static IEnumerator<TaskContract> TestDefaultValue()
{
for (int i = 0; i < 2; i++)
{ //Tests shared defaults
var block = Block.PlaceNew(BlockIDs.Cube, 1);
while (!block.Exists)
yield return Yield.It;
block.Remove();
while (block.Exists)
yield return Yield.It;
if(!Assert.Equal(block.Position, default,
$"Block position default value {block.Position} is incorrect, should be 0.",
$"Block position default value {block.Position} matches default."))
yield break;
block.Position = 4;
}
}
[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 = (DampedSpring) newBlock; }, "Casting block to DampedSpring raised an exception: ", "Casting block to DampedSpring completed without issue.");
if (!Assert.CloseTo(b.Stiffness, 1f, $"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.SpringFrequency, 30f, $"DampedSpring.SpringFrequency {b.SpringFrequency} does not equal default value, possibly because it failed silently.", "DampedSpring.SpringFrequency is close enough to default.")) return;
if (!Assert.CloseTo(b.Damping, 30f, $"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;
}

View file

@ -1,71 +1,47 @@
namespace TechbloxModdingAPI.Blocks
{
using RobocraftX.Blocks;
using RobocraftX.Common;
using Svelto.ECS;
public class DampedSpring : SignalingBlock
namespace TechbloxModdingAPI.Blocks
{
public class DampedSpring : Block
{
public DampedSpring(EGID id) : base(id)
{
}
public DampedSpring(uint id) : base(new EGID(id, CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP))
{
}
/// <summary>
/// Constructs a(n) DampedSpring object representing an existing block.
/// The spring frequency.
/// </summary>
public DampedSpring(EGID egid) :
base(egid)
public float SpringFrequency
{
get => BlockEngine.GetBlockInfo<DampedSpringReadOnlyStruct>(this).springFrequency;
set => BlockEngine.GetBlockInfo<DampedSpringReadOnlyStruct>(this).springFrequency = value;
}
/// <summary>
/// Constructs a(n) DampedSpring object representing an existing block.
/// </summary>
public DampedSpring(uint id) :
base(new EGID(id, CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP))
{
}
/// <summary>
/// Gets or sets the DampedSpring's Stiffness property. Tweakable stat.
/// </summary>
public float Stiffness
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).stiffness;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).stiffness = value;
}
}
/// <summary>
/// Gets or sets the DampedSpring's Damping property. Tweakable stat.
/// The spring's maximum damping force.
/// </summary>
public float Damping
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).damping;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).damping = value;
}
get => BlockEngine.GetBlockInfo<DampedSpringReadOnlyStruct>(this).springDamping;
set => BlockEngine.GetBlockInfo<DampedSpringReadOnlyStruct>(this).springDamping = value;
}
/// <summary>
/// Gets or sets the DampedSpring's MaxExtension property. Tweakable stat.
/// The spring's maximum extension.
/// </summary>
public float MaxExtension
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.DampedSpringReadOnlyStruct>(this).maxExtent;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.DampedSpringReadOnlyStruct>(this).maxExtent = value;
}
get => BlockEngine.GetBlockInfo<DampedSpringReadOnlyStruct>(this).maxExtent;
set => BlockEngine.GetBlockInfo<DampedSpringReadOnlyStruct>(this).maxExtent = value;
}
}
}

View file

@ -1,382 +1,35 @@
namespace TechbloxModdingAPI.Blocks
{
using RobocraftX.Common;
using Svelto.ECS;
using Techblox.EngineBlock;
namespace TechbloxModdingAPI.Blocks
{
public class Engine : SignalingBlock
{
/// <summary>
/// Constructs a(n) Engine object representing an existing block.
/// </summary>
public Engine(EGID egid) :
base(egid)
public Engine(EGID id) : base(id)
{
}
/// <summary>
/// Constructs a(n) Engine object representing an existing block.
/// </summary>
public Engine(uint id) :
base(new EGID(id, CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP))
public Engine(uint id) : base(new EGID(id, CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP))
{
}
/*/// <summary> - TODO: Internal struct access
/// Gets or sets the Engine's On property. May not be saved.
/// </summary>
public bool On
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn = value;
}
get => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).engineOn;
set => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).engineOn = value;
}
public int TorqueDirection
{
get => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).torqueDirection;
set => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).torqueDirection = value;
}
/// <summary>
/// Gets or sets the Engine's CurrentGear property. May not be saved.
/// </summary>
public int CurrentGear
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentGear;
get => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).currentGear;
set => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).currentGear = value;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentGear = value;
}
}
/// <summary>
/// Gets or sets the Engine's GearChangeCountdown property. May not be saved.
/// </summary>
public float GearChangeCountdown
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).gearChangeCountdown;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).gearChangeCountdown = value;
}
}
/// <summary>
/// Gets or sets the Engine's CurrentRpmAV property. May not be saved.
/// </summary>
public float CurrentRpmAV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmAV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmAV = value;
}
}
/// <summary>
/// Gets or sets the Engine's CurrentRpmLV property. May not be saved.
/// </summary>
public float CurrentRpmLV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmLV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmLV = value;
}
}
/// <summary>
/// Gets or sets the Engine's TargetRpmAV property. May not be saved.
/// </summary>
public float TargetRpmAV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmAV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmAV = value;
}
}
/// <summary>
/// Gets or sets the Engine's TargetRpmLV property. May not be saved.
/// </summary>
public float TargetRpmLV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmLV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmLV = value;
}
}
/// <summary>
/// Gets or sets the Engine's CurrentTorque property. May not be saved.
/// </summary>
public float CurrentTorque
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentTorque;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentTorque = value;
}
}
/// <summary>
/// Gets or sets the Engine's TotalWheelVelocityAV property. May not be saved.
/// </summary>
public float TotalWheelVelocityAV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityAV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityAV = value;
}
}
/// <summary>
/// Gets or sets the Engine's TotalWheelVelocityLV property. May not be saved.
/// </summary>
public float TotalWheelVelocityLV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityLV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityLV = value;
}
}
/// <summary>
/// Gets or sets the Engine's TotalWheelCount property. May not be saved.
/// </summary>
public int TotalWheelCount
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelCount;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelCount = value;
}
}
/// <summary>
/// Gets or sets the Engine's LastGearUpInput property. May not be saved.
/// </summary>
public bool LastGearUpInput
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearUpInput;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearUpInput = value;
}
}
/// <summary>
/// Gets or sets the Engine's LastGearDownInput property. May not be saved.
/// </summary>
public bool LastGearDownInput
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearDownInput;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearDownInput = value;
}
}
/// <summary>
/// Gets or sets the Engine's ManualToAutoGearCoolOffCounter property. May not be saved.
/// </summary>
public float ManualToAutoGearCoolOffCounter
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).manualToAutoGearCoolOffCounter;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).manualToAutoGearCoolOffCounter = value;
}
}
/// <summary>
/// Gets or sets the Engine's Load property. May not be saved.
/// </summary>
public float Load
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).load;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).load = value;
}
}
/// <summary>
/// Gets or sets the Engine's Power property. Tweakable stat.
/// </summary>
public float Power
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).power;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).power = value;
}
}
/// <summary>
/// Gets or sets the Engine's AutomaticGears property. Tweakable stat.
/// </summary>
public bool AutomaticGears
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).automaticGears;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).automaticGears = value;
}
}
/// <summary>
/// Gets or sets the Engine's GearChangeTime property. May not be saved.
/// </summary>
public float GearChangeTime
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearChangeTime;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearChangeTime = value;
}
}
/// <summary>
/// Gets or sets the Engine's MinRpm property. May not be saved.
/// </summary>
public float MinRpm
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).minRpm;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).minRpm = value;
}
}
/// <summary>
/// Gets or sets the Engine's MaxRpm property. May not be saved.
/// </summary>
public float MaxRpm
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpm;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpm = value;
}
}
/// <summary>
/// Gets the Engine's GearDownRpms property. May not be saved.
/// </summary>
public float[] GearDownRpms
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearDownRpms.ToManagedArray<float>();
}
}
/// <summary>
/// Gets or sets the Engine's GearUpRpm property. May not be saved.
/// </summary>
public float GearUpRpm
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearUpRpm;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearUpRpm = value;
}
}
/// <summary>
/// Gets or sets the Engine's MaxRpmChange property. May not be saved.
/// </summary>
public float MaxRpmChange
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpmChange;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpmChange = value;
}
}
/// <summary>
/// Gets or sets the Engine's ManualToAutoGearCoolOffTime property. May not be saved.
/// </summary>
public float ManualToAutoGearCoolOffTime
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime = value;
}
}*/
}
}

View file

@ -48,8 +48,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
pickedBlock.pickedBlockEntityID = sourceID;
pickedBlock.placedBlockEntityID = targetID;
pickedBlock.placedBlockTweaksMustCopy = true;
if (entitiesDB.Exists<BlockTagEntityStruct>(pickedBlock.pickedBlockEntityID)
&& entitiesDB.Exists<BlockTagEntityStruct>(pickedBlock.placedBlockEntityID))
if (entitiesDB.Exists<DBEntityStruct>(pickedBlock.pickedBlockEntityID)
&& entitiesDB.Exists<DBEntityStruct>(pickedBlock.placedBlockEntityID))
{
copyFromBlock.Invoke(Patch.copyEngine, new object[] {pickedBlock.ID, pickedBlock});
@ -60,7 +60,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
copyToBlock.Invoke(Patch.copyEngine, new object[] {pickedBlock.ID, pickedBlock});
ExclusiveGroupStruct group = BuildModeWiresGroups.WIRES_COPY_GROUP + playerID;
ExclusiveGroupStruct group = WiresExclusiveGroups.WIRES_COPY_GROUP + playerID;
copyWireToBlock.Invoke(Patch.createWireEngine, new object[] {group, pickedBlock.ID});
pickedBlock.placedBlockTweaksMustCopy = false;

View file

@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using Gamecraft.ColourPalette;
using Gamecraft.TimeRunning;
using Gamecraft.Wires;
using RobocraftX.Blocks;
using RobocraftX.Common;
@ -13,16 +12,11 @@ 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;
using TechbloxModdingAPI.Utility.ECS;
using PrefabsID = RobocraftX.Common.PrefabsID;
namespace TechbloxModdingAPI.Blocks.Engines
{
@ -47,21 +41,22 @@ namespace TechbloxModdingAPI.Blocks.Engines
public Block[] GetConnectedBlocks(EGID blockID)
{
if (!BlockExists(blockID)) return Array.Empty<Block>();
if (!BlockExists(blockID)) return new Block[0];
Stack<EGID> cubeStack = new Stack<EGID>();
FasterList<EGID> cubes = new FasterList<EGID>(10);
var coll = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
foreach (var ((ecoll, count), _) in coll)
foreach (var (ecoll, _) in coll)
{
for(int i = 0; i < count; i++)
var ecollB = ecoll.ToBuffer();
for(int i = 0; i < ecoll.count; i++)
{
ecoll[i].areConnectionsAssigned = false;
ref var conn = ref ecollB.buffer[i];
conn.isProcessed = false;
}
}
//TODO: GetConnectedCubesUtility
/*ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubes,
(in GridConnectionsEntityStruct _) => false);*/
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubes,
(in GridConnectionsEntityStruct g) => { return false; });
var ret = new Block[cubes.count];
for (int i = 0; i < cubes.count; i++)
@ -72,7 +67,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
public float4 ConvertBlockColor(byte index) => index == byte.MaxValue
? new float4(-1f, -1f, -1f, -1f)
: entitiesDB.QueryEntity<PaletteEntryEntityStruct>(index,
ColourPaletteExclusiveGroups.COLOUR_PALETTE_GROUP).Colour;
CommonExclusiveGroups.COLOUR_PALETTE_GROUP).Colour;
public OptionalRef<T> GetBlockInfoOptional<T>(Block block) where T : unmanaged, IEntityComponent
{
@ -81,10 +76,6 @@ namespace TechbloxModdingAPI.Blocks.Engines
public ref T GetBlockInfo<T>(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<T>(block);
}
@ -98,35 +89,13 @@ namespace TechbloxModdingAPI.Blocks.Engines
return ref entitiesDB.QueryEntityOrDefault<T>(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<PositionEntityStruct>(id);
var rot = entitiesDB.QueryEntity<RotationEntityStruct>(id);
var scale = entitiesDB.QueryEntity<ScalingEntityStruct>(id);
var skew = entitiesDB.QueryEntity<SkewComponent>(id);
entitiesDB.QueryEntity<RenderingDataStruct>(id).matrix =
math.mul(float4x4.TRS(pos.position, rot.rotation, scale.scale), skew.skewMatrix);
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(id); // Signal a prefab change so it updates the render buffers
entitiesDB.QueryEntity<RenderingDataStruct>(id).matrix = float4x4.TRS(pos.position, rot.rotation, scale.scale);
}
internal void UpdatePrefab(Block block, byte material, bool flipped)
@ -137,58 +106,50 @@ namespace TechbloxModdingAPI.Blocks.Engines
: uint.MaxValue;
if (prefabAssetID == uint.MaxValue)
{
if (entitiesDB.QueryEntityOptional<BlockTagEntityStruct>(block)) //The block exists
if (entitiesDB.QueryEntityOptional<DBEntityStruct>(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);
PrefabsID.GetOrCreatePrefabID((ushort) prefabAssetID, material, 1, flipped);
entitiesDB.QueryEntityOrDefault<GFXPrefabEntityStructGPUI>(block).prefabID = prefabId;
if (block.Exists)
{
entitiesDB.PublishEntityChangeDelayed<CubeMaterialStruct>(block.Id);
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(block.Id);
ref BuildingActionComponent local =
ref entitiesDB.QueryEntity<BuildingActionComponent>(BuildingDroneUtility
.GetLocalBuildingDrone(entitiesDB).ToEGID(entitiesDB));
local.buildAction = BuildAction.ChangeMaterial;
local.targetPosition = block.Position;
this.entitiesDB.PublishEntityChangeDelayed<BuildingActionComponent>(local.ID);
}
entitiesDB.PublishEntityChange<GFXPrefabEntityStructGPUI>(block.Id);
//Phyiscs prefab: prefabAssetID, set on block creation from the CubeListData
}
public void UpdateBlockColor(EGID id)
{
entitiesDB.PublishEntityChangeDelayed<ColourParameterEntityStruct>(id);
/*Console.WriteLine("Materials:\n" + FullGameFields._dataDb.GetValues<MaterialPropertiesData>()
.Select(kv => $"{kv.Key}: {((MaterialPropertiesData) kv.Value).Name}")
.Aggregate((a, b) => a + "\n" + b));*/
}
public bool BlockExists(EGID blockID)
{
return entitiesDB.Exists<BlockTagEntityStruct>(blockID);
return entitiesDB.Exists<DBEntityStruct>(blockID);
}
public SimBody[] GetSimBodiesFromID(byte id)
{
var ret = new FasterList<SimBody>(4);
var oids = entitiesDB.QueryEntitiesOptional<ObjectIdEntityStruct>(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP);
var oide = entitiesDB.QueryEntities<ObjectIdEntityStruct>();
EGIDMapper<GridConnectionsEntityStruct>? connections = null;
foreach (var oid in oids)
foreach (var ((oids, count), _) in oide)
{
if (oid.Get().objectId != id) continue;
for (int i = 0; i < count; i++)
{
ref ObjectIdEntityStruct oid = ref oids[i];
if (oid.objectId != id) continue;
if (!connections.HasValue) //Would need reflection to get the group from the build group otherwise
connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(oid.EGID.groupID);
//var rid = connections.Value.Entity(tag.ID.entityID).machineRigidBodyId;
/*foreach (var rb in ret) - TODO
connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(oid.ID.groupID);
var rid = connections.Value.Entity(oid.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: ;*/
DUPLICATE: ;
}
}
return ret.ToArray();
@ -196,14 +157,14 @@ namespace TechbloxModdingAPI.Blocks.Engines
public SimBody[] GetConnectedSimBodies(uint id)
{
var (joints, count) = entitiesDB.QueryEntities<JointEntityStruct>(MachineSimulationGroups.JOINTS_GROUP);
var joints = entitiesDB.QueryEntities<JointEntityStruct>(MachineSimulationGroups.JOINTS_GROUP).ToBuffer();
var list = new FasterList<SimBody>(4);
for (int i = 0; i < count; i++)
for (int i = 0; i < joints.count; i++)
{
ref var joint = ref joints[i];
ref var joint = ref joints.buffer[i];
if (joint.isBroken) continue;
/*if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB)); - TODO:
else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA));*/
if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB));
else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA));
}
return list.ToArray();
@ -213,13 +174,14 @@ namespace TechbloxModdingAPI.Blocks.Engines
{
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
var bodies = new HashSet<uint>();
foreach (var ((coll, count), _) in groups)
foreach (var (coll, _) in groups)
{
for (var index = 0; index < count; index++)
var array = coll.ToBuffer().buffer;
for (var index = 0; index < array.capacity; index++)
{
var conn = coll[index];
/*if (conn.clusterId == cid) - TODO
bodies.Add(conn.machineRigidBodyId);*/
var conn = array[index];
if (conn.clusterId == cid)
bodies.Add(conn.machineRigidBodyId);
}
}
@ -228,10 +190,10 @@ namespace TechbloxModdingAPI.Blocks.Engines
public EGID? FindBlockEGID(uint id)
{
var groups = entitiesDB.FindGroups<BlockTagEntityStruct>();
var groups = entitiesDB.FindGroups<DBEntityStruct>();
foreach (ExclusiveGroupStruct group in groups)
{
if (entitiesDB.Exists<BlockTagEntityStruct>(id, group))
if (entitiesDB.Exists<DBEntityStruct>(id, group))
return new EGID(id, group);
}
@ -241,14 +203,15 @@ namespace TechbloxModdingAPI.Blocks.Engines
public Cluster GetCluster(uint sbid)
{
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
foreach (var ((coll, count), _) in groups)
foreach (var (coll, _) in groups)
{
for (var index = 0; index < count; index++)
var array = coll.ToBuffer().buffer;
for (var index = 0; index < array.capacity; index++)
{
var conn = coll[index];
var conn = array[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) - TODO:
return new Cluster(conn.clusterId);*/
if (conn.machineRigidBodyId == sbid && conn.clusterId != uint.MaxValue)
return new Cluster(conn.clusterId);
}
}
@ -257,36 +220,27 @@ namespace TechbloxModdingAPI.Blocks.Engines
public Block[] GetBodyBlocks(uint sbid)
{
var groups = entitiesDB.FindGroups<GridConnectionsEntityStruct>();
groups = new QueryGroups(groups).Except(CommonExclusiveGroups.DISABLED_JOINTS_IN_SIM_GROUP).Evaluate().result;
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
var set = new HashSet<Block>();
foreach (var ((coll, tags, count), _) in entitiesDB.QueryEntities<GridConnectionsEntityStruct, BlockTagEntityStruct>(groups))
foreach (var (coll, _) in groups)
{
for (var index = 0; index < count; index++)
var array = coll.ToBuffer().buffer;
for (var index = 0; index < array.capacity; index++)
{
var conn = coll[index];
/*if (conn.machineRigidBodyId == sbid) - TODO
set.Add(Block.New(tags[index].ID));*/
var conn = array[index];
if (conn.machineRigidBodyId == sbid)
set.Add(Block.New(conn.ID));
}
}
return set.ToArray();
}
public ObjectID[] GetObjectIDsFromID(byte id)
#if DEBUG
public EntitiesDB GetEntitiesDB()
{
if (!entitiesDB.HasAny<ObjectIDTweakableComponent>(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP))
return Array.Empty<ObjectID>();
var ret = new FasterList<ObjectID>(4);
var oids = entitiesDB.QueryEntitiesOptional<ObjectIDTweakableComponent>(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP);
foreach (var oid in oids)
{
if (oid.Get().objectIDToTrigger == id)
ret.Add(new ObjectID(oid.EGID));
}
return ret.ToArray();
return entitiesDB;
}
#endif
}
}

View file

@ -10,8 +10,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
{
public class BlockEventsEngine : IReactionaryEngine<BlockTagEntityStruct>
{
public WrappedHandler<BlockPlacedRemovedEventArgs> Placed;
public WrappedHandler<BlockPlacedRemovedEventArgs> Removed;
public event EventHandler<BlockPlacedRemovedEventArgs> Placed;
public event EventHandler<BlockPlacedRemovedEventArgs> Removed;
public void Ready()
{
@ -26,14 +26,21 @@ namespace TechbloxModdingAPI.Blocks.Engines
public string Name { get; } = "TechbloxModdingAPIBlockEventsEngine";
public bool isRemovable { get; } = false;
private bool shouldAddRemove;
public void Add(ref BlockTagEntityStruct entityComponent, EGID egid)
{
Placed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid});
if (!(shouldAddRemove = !shouldAddRemove))
return;
ExceptionUtil.InvokeEvent(Placed, this,
new BlockPlacedRemovedEventArgs {ID = egid});
}
public void Remove(ref BlockTagEntityStruct entityComponent, EGID egid)
{
Removed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid});
if (!(shouldAddRemove = !shouldAddRemove))
return;
ExceptionUtil.InvokeEvent(Removed, this,
new BlockPlacedRemovedEventArgs {ID = egid});
}
}
@ -42,6 +49,6 @@ namespace TechbloxModdingAPI.Blocks.Engines
public EGID ID;
private Block block;
public Block Block => block ??= Block.New(ID);
public Block Block => block ?? (block = Block.New(ID));
}
}

View file

@ -1,27 +1,20 @@
using System;
using System;
using System.Collections.Generic;
using System.Reflection;
using Gamecraft.Blocks.BlockGroups;
using Gamecraft.GUI.Blueprints;
using HarmonyLib;
using RobocraftX.Blocks;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Common;
using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.CR.MachineEditing.BoxSelect.ClipboardOperations;
using RobocraftX.Physics;
using RobocraftX.Rendering;
using RobocraftX.Rendering.GPUI;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.DataStructures;
using Svelto.ECS.EntityStructs;
using Svelto.ECS.Native;
using Svelto.ECS.Serialization;
using Techblox.Blocks.Connections;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
@ -49,8 +42,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
private static readonly MethodInfo SerializeGhostBlueprint =
AccessTools.Method(SerializeGhostBlueprintType, "SerializeClipboardGhostEntities");
private static NativeEntityRemove nativeBlockRemove;
private static NativeEntityRemove nativeConnectionRemove;
private static NativeEntityRemove nativeRemove;
private static MachineGraphConnectionEntityFactory connectionFactory;
private static IEntityFunctions entityFunctions;
private static ClipboardSerializationDataResourceManager clipboardManager;
@ -90,8 +82,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
public void RemoveBlockGroup(int id)
{
BlockGroupUtility.RemoveAllBlocksInBlockGroup(id, entitiesDB, removedConnections, nativeBlockRemove,
nativeConnectionRemove, connectionFactory, default).Complete();
BlockGroupUtility.RemoveAllBlocksInBlockGroup(id, entitiesDB, removedConnections, nativeRemove,
connectionFactory, default).Complete();
}
public int CreateBlockGroup(float3 position, quaternion rotation)
@ -168,15 +160,10 @@ namespace TechbloxModdingAPI.Blocks.Engines
private void BuildGhostBlueprint(ICollection<Block> blocks, float3 pos, quaternion rot, uint playerID)
{
GhostChildUtility.ClearGhostChildren(playerID, entitiesDB, entityFunctions);
var bssesopt = entitiesDB.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(playerID,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup));
if (!bssesopt)
return;
foreach (var block in blocks)
{
GhostChildUtility.BuildGhostChild(in playerID, block.Id, in pos, in rot, entitiesDB,
BuildGhostBlueprintFactory, false, bssesopt.Get().buildingDroneReference,
FullGameFields._managers.blockLabelResourceManager);
BuildGhostBlueprintFactory, false);
}
}
@ -261,77 +248,6 @@ namespace TechbloxModdingAPI.Blocks.Engines
clipboardManager.DecrementRefCount(blueprintID);
}
//GhostChildUtility.BuildGhostChild
public Block BuildGhostChild()
{
var sourceId = new EGID(Player.LocalPlayer.Id, GHOST_BLOCKS_ENABLED.Group);
var positionEntityStruct = entitiesDB.QueryEntity<PositionEntityStruct>(sourceId);
var rotationEntityStruct = entitiesDB.QueryEntity<RotationEntityStruct>(sourceId);
var scalingEntityStruct = entitiesDB.QueryEntity<ScalingEntityStruct>(sourceId);
var dbStruct = entitiesDB.QueryEntity<DBEntityStruct>(sourceId);
var colliderStruct = entitiesDB.QueryEntity<ColliderAabb>(sourceId);
var colorStruct = entitiesDB.QueryEntity<ColourParameterEntityStruct>(sourceId);
uint ghostChildBlockId = CommonExclusiveGroups.GetNewGhostChildBlockID();
var ghostEntityReference = GhostBlockUtils.GetGhostEntityReference(sourceId.entityID, entitiesDB);
var entityInitializer = BuildGhostBlueprintFactory.Build(
new EGID(ghostChildBlockId, BoxSelectExclusiveGroups.GhostChildEntitiesExclusiveGroup), /*dbStruct.DBID*/ (uint)BlockIDs.Cube,
FullGameFields._managers.blockLabelResourceManager);
entityInitializer.Init(dbStruct);
entityInitializer.Init(new GFXPrefabEntityStructGPUI(
PrefabsID.GetOrAddPrefabID((ushort)entityInitializer.Get<PrefabAssetIDComponent>().prefabAssetID,
entitiesDB.QueryEntity<CubeMaterialStruct>(sourceId).materialId, 7,
FlippedBlockUtils.IsFlipped(in scalingEntityStruct.scale)), true));
entityInitializer.Init(entitiesDB.QueryEntity<CubeMaterialStruct>(sourceId));
entityInitializer.Init(new GhostParentEntityStruct
{
ghostBlockParentEntityReference = ghostEntityReference,
ownerMustSerializeOnAdd = false
});
entityInitializer.Init(colorStruct);
entityInitializer.Init(colliderStruct);
entityInitializer.Init(new RigidBodyEntityStruct
{
position = positionEntityStruct.position,
rotation = rotationEntityStruct.rotation
});
entityInitializer.Init(new ScalingEntityStruct
{
scale = scalingEntityStruct.scale
});
entityInitializer.Init(new LocalTransformEntityStruct
{
position = positionEntityStruct.position,
rotation = rotationEntityStruct.rotation
});
entityInitializer.Init(new RotationEntityStruct
{
rotation = rotationEntityStruct.rotation
});
entityInitializer.Init(new PositionEntityStruct
{
position = positionEntityStruct.position
});
entityInitializer.Init(new SkewComponent
{
skewMatrix = entitiesDB.QueryEntity<SkewComponent>(sourceId).skewMatrix
});
entityInitializer.Init(new BlockPlacementInfoStruct
{
placedByBuildingDrone = entitiesDB
.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(Player.LocalPlayer.Id,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup)).Get().buildingDroneReference
});
entityInitializer.Init(new GridRotationStruct
{
position = float3.zero,
rotation = quaternion.identity
});
var block = Block.New(entityInitializer.EGID);
block.InitData = entityInitializer;
return block;
}
public string Name { get; } = "TechbloxModdingAPIBlueprintGameEngine";
public bool isRemovable { get; } = false;
@ -341,8 +257,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
public static void Prefix(IEntityFunctions entityFunctions,
MachineGraphConnectionEntityFactory machineGraphConnectionEntityFactory)
{
nativeBlockRemove = entityFunctions.ToNativeRemove<BlockEntityDescriptor>("TBAPI" + nameof(BlueprintEngine));
nativeConnectionRemove = entityFunctions.ToNativeRemove<MachineConnectionEntityDescriptor>("TBAPI" + nameof(BlueprintEngine));
nativeRemove = entityFunctions.ToNativeRemove<BlockEntityDescriptor>("GCAPI" + nameof(BlueprintEngine));
connectionFactory = machineGraphConnectionEntityFactory;
BlueprintEngine.entityFunctions = entityFunctions;
}

View file

@ -1,5 +1,5 @@
using RobocraftX.Common;
using RobocraftX.DOTS;
using RobocraftX.UECS;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Mathematics;
@ -7,7 +7,6 @@ using Unity.Transforms;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines
{
@ -41,7 +40,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntityOrDefault<PositionEntityStruct>(block);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(block);
var phyStruct = this.entitiesDB.QueryEntityOptional<DOTSPhysicsEntityStruct>(block);
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntityOrDefault<UECSPhysicsEntityStruct>(block);
// main (persistent) position
posStruct.position = vector;
// placement grid position
@ -49,15 +48,15 @@ namespace TechbloxModdingAPI.Blocks.Engines
// rendered position
transStruct.position = vector;
// collision position
if (phyStruct)
if (phyStruct.ID != EGID.Empty)
{ //It exists
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.Get().dotsEntity, new Translation
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Translation
{
Value = posStruct.position
});
}
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).areConnectionsAssigned = false;
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).isProcessed = false;
return posStruct.position;
}

View file

@ -7,7 +7,6 @@ using HarmonyLib;
using RobocraftX.Blocks;
using RobocraftX.Character;
using RobocraftX.Common;
using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.Rendering;
using RobocraftX.Rendering.GPUI;
using Svelto.ECS;
@ -16,7 +15,6 @@ using Unity.Mathematics;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines
{
@ -55,11 +53,11 @@ namespace TechbloxModdingAPI.Blocks.Engines
//RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine
DBEntityStruct dbEntity = new DBEntityStruct {DBID = block};
EntityInitializer structInitializer = _blockEntityFactory.Build(CommonExclusiveGroups.blockIDGeneratorClient.Next(), block); //The ghost block index is only used for triggers
EntityInitializer structInitializer = _blockEntityFactory.Build(CommonExclusiveGroups.nextBlockEntityID, block); //The ghost block index is only used for triggers
uint prefabAssetID = structInitializer.Has<PrefabAssetIDComponent>()
? structInitializer.Get<PrefabAssetIDComponent>().prefabAssetID
: throw new BlockException("Prefab asset ID not found!"); //Set by the game
uint prefabId = PrefabsID.GetOrAddPrefabID((ushort) prefabAssetID, (byte) BlockMaterial.SteelBodywork, 1, false);
uint prefabId = PrefabsID.GetOrCreatePrefabID((ushort) prefabAssetID, (byte) BlockMaterial.SteelBodywork, 1, false);
structInitializer.Init(new GFXPrefabEntityStructGPUI(prefabId));
structInitializer.Init(dbEntity);
structInitializer.Init(new PositionEntityStruct {position = position});
@ -72,14 +70,10 @@ namespace TechbloxModdingAPI.Blocks.Engines
});
structInitializer.Init(new UniformBlockScaleEntityStruct {scaleFactor = 1});
structInitializer.Get<CubeMaterialStruct>().materialId = (byte) BlockMaterial.SteelBodywork;
var bssesopt = entitiesDB.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(playerId,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup));
if (!bssesopt)
throw new BlockException("Invalid player ID specified for block placement");
structInitializer.Init(new BlockPlacementInfoStruct
{
loadedFromDisk = false,
placedByBuildingDrone = bssesopt.Get().buildingDroneReference,
placedBy = playerId,
triggerAutoWiring = autoWire && structInitializer.Has<BlockPortsStruct>()
});

View file

@ -1,61 +1,45 @@
using System.Reflection;
using Gamecraft.Blocks.BlockGroups;
using HarmonyLib;
using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.GroupTags;
using RobocraftX.StateSync;
using Svelto.ECS;
using Svelto.ECS.Native;
using Techblox.Blocks.Connections;
using Unity.Collections;
using Unity.Jobs;
using Allocator = Unity.Collections.Allocator;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks.Engines
{
public class RemovalEngine : IApiEngine, IDeterministicTimeStopped
public class RemovalEngine : IApiEngine
{
private static IEntityFunctions _entityFunctions;
private static MachineGraphConnectionEntityFactory _connectionFactory;
private NativeHashSet<ulong> removedConnections;
public bool RemoveBlock(EGID target)
{
if (!entitiesDB.Exists<MachineGraphConnectionsEntityStruct>(target))
return false;
using var connStructMapper =
entitiesDB.QueryNativeMappedEntities<MachineGraphConnectionsEntityStruct>(GroupTag<BLOCK_TAG>.Groups,
Svelto.Common.Allocator.Temp);
if (entitiesDB.TryQueryNativeMappedEntities<MachineConnectionComponent>(
ConnectionsExclusiveGroups.MACHINE_CONNECTION_GROUP, out var mapper))
{
BlockGroupUtility.RemoveBlockConnections(target, removedConnections, _connectionFactory,
connStructMapper, mapper, entitiesDB.GetEntityReferenceMap(), _entityFunctions);
}
var connections = entitiesDB.QueryEntity<MachineGraphConnectionsEntityStruct>(target);
var groups = entitiesDB.FindGroups<MachineGraphConnectionsEntityStruct>();
var connStructMapper =
entitiesDB.QueryNativeMappedEntities<MachineGraphConnectionsEntityStruct>(groups);
for (int i = connections.connections.Count<MachineConnectionStruct>() - 1; i >= 0; i--)
_connectionFactory.RemoveConnection(connections, i, connStructMapper);
_entityFunctions.RemoveEntity<BlockEntityDescriptor>(target);
return true;
}
public void Ready()
{
removedConnections = new(2000, Allocator.Persistent);
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
removedConnections.Dispose();
}
public string Name => "TechbloxModdingAPIRemovalGameEngine";
public string name => Name;
public string Name { get; } = "TechbloxModdingAPIRemovalGameEngine";
public bool isRemovable => false;
@ -75,12 +59,5 @@ namespace TechbloxModdingAPI.Blocks.Engines
return AccessTools.TypeByName("RobocraftX.CR.MachineEditing.RemoveBlockEngine").GetConstructors()[0];
}
}
public JobHandle DeterministicStep(in float deltaTime, JobHandle inputDeps)
{
if (removedConnections.IsCreated)
removedConnections.Clear();
return default;
}
}
}

View file

@ -1,5 +1,5 @@
using RobocraftX.Common;
using RobocraftX.DOTS;
using RobocraftX.UECS;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Mathematics;
@ -7,7 +7,6 @@ using UnityEngine;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines
{
@ -41,7 +40,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(block);
var phyStruct = this.entitiesDB.QueryEntityOptional<DOTSPhysicsEntityStruct>(block);
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntityOrDefault<UECSPhysicsEntityStruct>(block);
// main (persistent) rotation
Quaternion newRotation = rotStruct.rotation;
newRotation.eulerAngles = vector;
@ -51,18 +50,16 @@ namespace TechbloxModdingAPI.Blocks.Engines
// rendered rotation
transStruct.rotation = newRotation;
// collision rotation
if (phyStruct)
if (phyStruct.ID != EGID.Empty)
{ //It exists
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.Get().dotsEntity,
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity,
new Unity.Transforms.Rotation
{
Value = rotStruct.rotation
});
}
// TODO: Connections probably need to be assigned (maybe)
// They are assigned during machine processing anyway
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).areConnectionsAssigned = false;
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).isProcessed = false;
return ((Quaternion)rotStruct.rotation).eulerAngles;
}

View file

@ -2,7 +2,7 @@
using HarmonyLib;
using RobocraftX.Common;
using RobocraftX.DOTS;
using RobocraftX.UECS;
using Svelto.ECS;
using Unity.Entities;
@ -13,7 +13,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
{
public class ScalingEngine : IApiEngine
{
private static IReactOnAddAndRemove<DOTSPhysicsEntityCreationStruct> physicsEngine;
private static IReactOnAddAndRemove<UECSPhysicsEntityCreationStruct> physicsEngine;
public void Ready()
{
@ -34,16 +34,16 @@ namespace TechbloxModdingAPI.Blocks.Engines
if (_entityManager == default)
_entityManager = FullGameFields._physicsWorld.EntityManager;
//Assuming the block exists
var entity = entitiesDB.QueryEntity<DOTSPhysicsEntityStruct>(egid).dotsEntity;
var pes = new DOTSPhysicsEntityCreationStruct();
physicsEngine.Add(ref pes, egid); //Create new DOTS entity
var entity = entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(egid).uecsEntity;
var pes = new UECSPhysicsEntityCreationStruct();
physicsEngine.Add(ref pes, egid); //Create new UECS entity
_entityManager.DestroyEntity(entity);
}
[HarmonyPatch]
class PhysicsEnginePatch
{
static void Postfix(IReactOnAddAndRemove<DOTSPhysicsEntityCreationStruct> __instance)
static void Postfix(IReactOnAddAndRemove<UECSPhysicsEntityCreationStruct> __instance)
{
physicsEngine = __instance;
Logging.MetaDebugLog("Physics engine injected.");
@ -51,7 +51,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
static MethodBase TargetMethod(Harmony instance)
{
return AccessTools.Method("RobocraftX.StateSync.HandleDOTSPhysicEntitiesWithPrefabCreationEngine" +
return AccessTools.Method("RobocraftX.StateSync.HandleUECSPhysicEntitiesWithPrefabCreationEngine" +
":Ready");
}
}

View file

@ -6,7 +6,6 @@ using Svelto.ECS;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines
{
@ -42,18 +41,19 @@ namespace TechbloxModdingAPI.Blocks.Engines
// implementations for block wiring
public (WireEntityStruct Wire, EGID ID) CreateNewWire(EGID startBlock, byte startPort, EGID endBlock, byte endPort)
public WireEntityStruct CreateNewWire(EGID startBlock, byte startPort, EGID endBlock, byte endPort)
{
EGID wireEGID = new EGID(BuildModeWiresGroups.NewWireEntityId, BuildModeWiresGroups.WiresGroup.Group);
EGID wireEGID = new EGID(WiresExclusiveGroups.NewWireEntityId, NamedExclusiveGroup<WiresGroup>.Group);
EntityInitializer wireInitializer = Factory.BuildEntity<WireEntityDescriptor>(wireEGID);
wireInitializer.Init(new WireEntityStruct
{
sourceBlockEGID = startBlock,
sourcePortUsage = startPort,
destinationBlockEGID = endBlock,
destinationPortUsage = endPort
destinationPortUsage = endPort,
ID = wireEGID
});
return (wireInitializer.Get<WireEntityStruct>(), wireEGID);
return wireInitializer.Get<WireEntityStruct>();
}
public ref WireEntityStruct GetWire(EGID wire)
@ -77,8 +77,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
public ref PortEntityStruct GetPortByOffset(BlockPortsStruct bps, byte portNumber, bool input)
{
ExclusiveGroup group = input
? NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group
: NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group;
? NamedExclusiveGroup<InputPortsGroup>.Group
: NamedExclusiveGroup<OutputPortsGroup>.Group;
uint id = (input ? bps.firstInputID : bps.firstOutputID) + portNumber;
EGID egid = new EGID(id, group);
if (!entitiesDB.Exists<PortEntityStruct>(egid))
@ -116,8 +116,9 @@ namespace TechbloxModdingAPI.Blocks.Engines
public bool SetSignal(uint signalID, float signal, bool input = true)
{
var (array, count) = GetSignalStruct(signalID, out uint index, input);
if (count > 0) array[index].valueAsFloat = signal;
var array = GetSignalStruct(signalID, out uint index, input);
var arrayB = array.ToBuffer();
if (array.count > 0) arrayB.buffer[index].valueAsFloat = signal;
return false;
}
@ -129,10 +130,11 @@ namespace TechbloxModdingAPI.Blocks.Engines
public float AddSignal(uint signalID, float signal, bool clamp = true, bool input = true)
{
var (array, count) = GetSignalStruct(signalID, out uint index, input);
if (count > 0)
var array = GetSignalStruct(signalID, out uint index, input);
var arrayB = array.ToBuffer();
if (array.count > 0)
{
ref var channelData = ref array[index];
ref var channelData = ref arrayB.buffer[index];
channelData.valueAsFloat += signal;
if (clamp)
{
@ -160,8 +162,9 @@ namespace TechbloxModdingAPI.Blocks.Engines
public float GetSignal(uint signalID, bool input = true)
{
var (array, count) = GetSignalStruct(signalID, out uint index, input);
return count > 0 ? array[index].valueAsFloat : 0f;
var array = GetSignalStruct(signalID, out uint index, input);
var arrayB = array.ToBuffer();
return array.count > 0 ? arrayB.buffer[index].valueAsFloat : 0f;
}
public uint[] GetSignalIDs(EGID blockID, bool input = true)
@ -190,7 +193,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
EGID[] inputs = new EGID[ports.inputCount];
for (uint i = 0; i < ports.inputCount; i++)
{
inputs[i] = new EGID(i + ports.firstInputID, NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group);
inputs[i] = new EGID(i + ports.firstInputID, NamedExclusiveGroup<InputPortsGroup>.Group);
}
return inputs;
}
@ -201,55 +204,70 @@ namespace TechbloxModdingAPI.Blocks.Engines
EGID[] outputs = new EGID[ports.outputCount];
for (uint i = 0; i < ports.outputCount; i++)
{
outputs[i] = new EGID(i + ports.firstOutputID, NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group);
outputs[i] = new EGID(i + ports.firstOutputID, NamedExclusiveGroup<OutputPortsGroup>.Group);
}
return outputs;
}
public OptionalRef<PortEntityStruct> MatchBlockIOToPort(Block block, byte portUsage, bool output)
public EGID MatchBlockInputToPort(Block block, byte portUsage, out bool exists)
{
return MatchBlockIOToPort(block.Id, portUsage, output);
var ports = entitiesDB.QueryEntityOptional<BlockPortsStruct>(block);
exists = ports;
return new EGID(ports.Get().firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group);
}
public OptionalRef<PortEntityStruct> MatchBlockIOToPort(EGID block, byte portUsage, bool output)
public EGID MatchBlockInputToPort(EGID block, byte portUsage, out bool exists)
{
if (!entitiesDB.Exists<BlockPortsStruct>(block))
{
exists = false;
return default;
var group = output
? NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group
: NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group;
}
exists = true;
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
if (!entitiesDB.TryQueryMappedEntities<PortEntityStruct>(group, out var mapper))
return default;
for (uint i = 0; i < (output ? ports.outputCount : ports.inputCount); ++i)
{
uint entityID = (output ? ports.firstOutputID : ports.firstInputID) + i;
if (!mapper.TryGetArrayAndEntityIndex(entityID, out var index, out var array) ||
array[index].usage != portUsage) continue;
return new OptionalRef<PortEntityStruct>(array, index, new EGID(entityID, group));
return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group);
}
public EGID MatchBlockOutputToPort(Block block, byte portUsage, out bool exists)
{
var ports = entitiesDB.QueryEntityOptional<BlockPortsStruct>(block);
exists = ports;
return new EGID(ports.Get().firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group);
}
public EGID MatchBlockOutputToPort(EGID block, byte portUsage, out bool exists)
{
if (!entitiesDB.Exists<BlockPortsStruct>(block))
{
exists = false;
return default;
}
public OptionalRef<WireEntityStruct> MatchPortToWire(PortEntityStruct port, EGID blockID, out EGID wireID)
{
var (wires, ids, count) = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group);
for (uint i = 0; i < count; i++)
{
if ((wires[i].destinationPortUsage == port.usage && wires[i].destinationBlockEGID == blockID)
|| (wires[i].sourcePortUsage == port.usage && wires[i].sourceBlockEGID == blockID))
{
wireID = new EGID(ids[i], BuildModeWiresGroups.WiresGroup.Group);
return new OptionalRef<WireEntityStruct>(wires, i);
}
exists = true;
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group);
}
wireID = default;
return default;
public ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists)
{
ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID);
var wires = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<WiresGroup>.Group);
var wiresB = wires.ToBuffer().buffer;
for (uint i = 0; i < wires.count; i++)
{
if ((wiresB[i].destinationPortUsage == port.usage && wiresB[i].destinationBlockEGID == blockID)
|| (wiresB[i].sourcePortUsage == port.usage && wiresB[i].sourceBlockEGID == blockID))
{
exists = true;
return ref wiresB[i];
}
}
exists = false;
WireEntityStruct[] defRef = new WireEntityStruct[1];
return ref defRef[0];
}
public EGID MatchBlocksToWire(EGID startBlock, EGID endBlock, byte startPort = byte.MaxValue, byte endPort = byte.MaxValue)
public ref WireEntityStruct MatchBlocksToWire(EGID startBlock, EGID endBlock, out bool exists, byte startPort = byte.MaxValue,
byte endPort = byte.MaxValue)
{
EGID[] startPorts;
if (startPort == byte.MaxValue)
@ -260,7 +278,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
else
{
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(startBlock);
startPorts = new EGID[] {new EGID(ports.firstOutputID + startPort, NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group) };
startPorts = new EGID[] {new EGID(ports.firstOutputID + startPort, NamedExclusiveGroup<OutputPortsGroup>.Group) };
}
EGID[] endPorts;
@ -272,49 +290,59 @@ namespace TechbloxModdingAPI.Blocks.Engines
else
{
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(endBlock);
endPorts = new EGID[] {new EGID(ports.firstInputID + endPort, NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group) };
endPorts = new EGID[] {new EGID(ports.firstInputID + endPort, NamedExclusiveGroup<InputPortsGroup>.Group) };
}
EntityCollection<WireEntityStruct> wires = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<WiresGroup>.Group);
var wiresB = wires.ToBuffer().buffer;
for (int endIndex = 0; endIndex < endPorts.Length; endIndex++)
{
PortEntityStruct endPES = entitiesDB.QueryEntity<PortEntityStruct>(endPorts[endIndex]);
for (int startIndex = 0; startIndex < startPorts.Length; startIndex++)
{
PortEntityStruct startPES = entitiesDB.QueryEntity<PortEntityStruct>(startPorts[startIndex]);
foreach (var wireOpt in entitiesDB.QueryEntitiesOptional<WireEntityStruct>(
NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group))
for (int w = 0; w < wires.count; w++)
{
var wire = wireOpt.Get();
if ((wire.destinationPortUsage == endPES.usage && wire.destinationBlockEGID == endBlock)
&& (wire.sourcePortUsage == startPES.usage && wire.sourceBlockEGID == startBlock))
if ((wiresB[w].destinationPortUsage == endPES.usage && wiresB[w].destinationBlockEGID == endBlock)
&& (wiresB[w].sourcePortUsage == startPES.usage && wiresB[w].sourceBlockEGID == startBlock))
{
return wireOpt.EGID;
exists = true;
return ref wiresB[w];
}
}
}
}
return default;
exists = false;
WireEntityStruct[] defRef = new WireEntityStruct[1];
return ref defRef[0];
}
public OptionalRef<ChannelDataStruct> GetChannelDataStruct(EGID portID)
public ref ChannelDataStruct GetChannelDataStruct(EGID portID, out bool exists)
{
var port = GetPort(portID);
var (channels, count) = entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<BuildModeWiresGroups.ChannelDataGroup>.Group);
return port.firstChannelIndexCachedInSim < count
? new OptionalRef<ChannelDataStruct>(channels, port.firstChannelIndexCachedInSim)
: default;
ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID);
var channels = entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<ChannelDataGroup>.Group);
var channelsB = channels.ToBuffer();
if (port.firstChannelIndexCachedInSim < channels.count)
{
exists = true;
return ref channelsB.buffer[port.firstChannelIndexCachedInSim];
}
exists = false;
ChannelDataStruct[] defRef = new ChannelDataStruct[1];
return ref defRef[0];
}
public EGID[] GetElectricBlocks()
{
var res = new FasterList<EGID>();
foreach (var ((coll, ids, count), _) in entitiesDB.QueryEntities<BlockPortsStruct>())
foreach (var (coll, _) in entitiesDB.QueryEntities<BlockPortsStruct>())
{
for (int i = 0; i < count; i++)
var collB = coll.ToBuffer();
for (int i = 0; i < coll.count; i++)
{
ref BlockPortsStruct s = ref coll[i];
//res.Add(s.ID); - TODO: Would need to search for the groups for each block
ref BlockPortsStruct s = ref collB.buffer[i];
res.Add(s.ID);
}
}
@ -323,30 +351,55 @@ namespace TechbloxModdingAPI.Blocks.Engines
public EGID[] WiredToInput(EGID block, byte port)
{
return entitiesDB
.QueryEntitiesOptional<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group)
.ToArray(wire => wire.ID,
wire => wire.Component.destinationPortUsage == port && wire.Component.destinationBlockEGID == block);
WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup<WiresGroup>.Group,
(WireEntityStruct wes) => wes.destinationPortUsage == port && wes.destinationBlockEGID == block);
EGID[] result = new EGID[wireEntityStructs.Length];
for (uint i = 0; i < wireEntityStructs.Length; i++)
{
result[i] = wireEntityStructs[i].ID;
}
return result;
}
public EGID[] WiredToOutput(EGID block, byte port)
{
return entitiesDB
.QueryEntitiesOptional<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group)
.ToArray(wire => wire.ID,
wire => wire.Component.sourcePortUsage == port && wire.Component.sourceBlockEGID == block);
WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup<WiresGroup>.Group,
(WireEntityStruct wes) => wes.sourcePortUsage == port && wes.sourceBlockEGID == block);
EGID[] result = new EGID[wireEntityStructs.Length];
for (uint i = 0; i < wireEntityStructs.Length; i++)
{
result[i] = wireEntityStructs[i].ID;
}
return result;
}
private T[] Search<T>(ExclusiveGroup group, Func<T, bool> isMatch) where T : unmanaged, IEntityComponent
{
FasterList<T> results = new FasterList<T>();
EntityCollection<T> components = entitiesDB.QueryEntities<T>(group);
var componentsB = components.ToBuffer();
for (uint i = 0; i < components.count; i++)
{
if (isMatch(componentsB.buffer[i]))
{
results.Add(componentsB.buffer[i]);
}
}
return results.ToArray();
}
private EntityCollection<ChannelDataStruct> GetSignalStruct(uint signalID, out uint index, bool input = true)
{
ExclusiveGroup group = input
? NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group
: NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group;
? NamedExclusiveGroup<InputPortsGroup>.Group
: NamedExclusiveGroup<OutputPortsGroup>.Group;
if (entitiesDB.Exists<PortEntityStruct>(signalID, group))
{
index = entitiesDB.QueryEntity<PortEntityStruct>(signalID, group).firstChannelIndexCachedInSim;
index = entitiesDB.QueryEntity<PortEntityStruct>(signalID, group).anyChannelIndex;
var channelData =
entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<BuildModeWiresGroups.ChannelDataGroup>.Group);
entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<ChannelDataGroup>.Group);
return channelData;
}

View file

@ -1,26 +0,0 @@
namespace TechbloxModdingAPI.Blocks
{
using RobocraftX.Common;
using Svelto.ECS;
public class LogicGate : SignalingBlock
{
/// <summary>
/// Constructs a(n) LogicGate object representing an existing block.
/// </summary>
public LogicGate(EGID egid) :
base(egid)
{
}
/// <summary>
/// Constructs a(n) LogicGate object representing an existing block.
/// </summary>
public LogicGate(uint id) :
base(new EGID(id, CommonExclusiveGroups.LOGIC_BLOCK_GROUP))
{
}
}
}

View file

@ -1,71 +0,0 @@
namespace TechbloxModdingAPI.Blocks
{
using RobocraftX.Common;
using Svelto.ECS;
public class Motor : SignalingBlock
{
/// <summary>
/// Constructs a(n) Motor object representing an existing block.
/// </summary>
public Motor(EGID egid) :
base(egid)
{
}
/// <summary>
/// Constructs a(n) Motor object representing an existing block.
/// </summary>
public Motor(uint id) :
base(new EGID(id, CommonExclusiveGroups.MOTOR_BLOCK_GROUP))
{
}
/// <summary>
/// Gets or sets the Motor's TopSpeed property. Tweakable stat.
/// </summary>
public float TopSpeed
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.MotorReadOnlyStruct>(this).maxVelocity;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.MotorReadOnlyStruct>(this).maxVelocity = value;
}
}
/// <summary>
/// Gets or sets the Motor's Torque property. Tweakable stat.
/// </summary>
public float Torque
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.MotorReadOnlyStruct>(this).maxForce;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.MotorReadOnlyStruct>(this).maxForce = value;
}
}
/// <summary>
/// Gets or sets the Motor's Reverse property. Tweakable stat.
/// </summary>
public bool Reverse
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.MotorReadOnlyStruct>(this).reverse;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.MotorReadOnlyStruct>(this).reverse = value;
}
}
}
}

View file

@ -1,50 +0,0 @@
using System;
using Techblox.ObjectIDBlockServer;
namespace TechbloxModdingAPI.Blocks
{
using Svelto.ECS;
public class ObjectID : SignalingBlock
{
/// <summary>
/// Constructs a(n) ObjectID object representing an existing block.
/// </summary>
public ObjectID(EGID egid) :
base(egid)
{
}
/// <summary>
/// Constructs a(n) ObjectID object representing an existing block.
/// </summary>
public ObjectID(uint id) :
base(new EGID(id, ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP))
{
}
/// <summary>
/// Gets or sets the ObjectID's Identifier property. Tweakable stat.
/// </summary>
public char Identifier
{
get => (char) (BlockEngine.GetBlockInfo<ObjectIDTweakableComponent>(this).objectIDToTrigger + 'A');
set
{
if(value is < 'A' or > 'Z')
throw new ArgumentOutOfRangeException(nameof(value), "ObjectIdentifier must be set to a letter between A and Z.");
BlockEngine.GetBlockInfo<ObjectIDTweakableComponent>(this).objectIDToTrigger = (byte) (value - 'A');
Label = value + ""; //The label isn't updated automatically
}
}
/// <summary>
/// Finds the identifier blocks with the given ID.
/// </summary>
/// <param name="id">The ID to look for</param>
/// <returns>An array that may be empty</returns>
public static ObjectID[] GetByID(char id) => BlockEngine.GetObjectIDsFromID((byte) (id - 'A'));
}
}

View file

@ -1,71 +0,0 @@
namespace TechbloxModdingAPI.Blocks
{
using RobocraftX.Common;
using Svelto.ECS;
public class Piston : SignalingBlock
{
/// <summary>
/// Constructs a(n) Piston object representing an existing block.
/// </summary>
public Piston(EGID egid) :
base(egid)
{
}
/// <summary>
/// Constructs a(n) Piston object representing an existing block.
/// </summary>
public Piston(uint id) :
base(new EGID(id, CommonExclusiveGroups.PISTON_BLOCK_GROUP))
{
}
/// <summary>
/// Gets or sets the Piston's MaximumForce property. Tweakable stat.
/// </summary>
public float MaximumForce
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.PistonReadOnlyStruct>(this).pistonVelocity;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.PistonReadOnlyStruct>(this).pistonVelocity = value;
}
}
/// <summary>
/// Gets or sets the Piston's MaxExtension property. Tweakable stat.
/// </summary>
public float MaxExtension
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.PistonReadOnlyStruct>(this).maxDeviation;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.PistonReadOnlyStruct>(this).maxDeviation = value;
}
}
/// <summary>
/// Gets or sets the Piston's InputIsExtension property. Tweakable stat.
/// </summary>
public bool InputIsExtension
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.PistonReadOnlyStruct>(this).hasProportionalInput;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.PistonReadOnlyStruct>(this).hasProportionalInput = value;
}
}
}
}

View file

@ -1,56 +0,0 @@
namespace TechbloxModdingAPI.Blocks
{
using RobocraftX.Common;
using Svelto.ECS;
public class Seat : SignalingBlock
{
/// <summary>
/// Constructs a(n) Seat object representing an existing block.
/// </summary>
public Seat(EGID egid) :
base(egid)
{
}
/// <summary>
/// Constructs a(n) Seat object representing an existing block.
/// </summary>
public Seat(uint id) :
base(new EGID(id, RobocraftX.PilotSeat.SeatGroups.PILOTSEAT_BLOCK_BUILD_GROUP))
{
}
/// <summary>
/// Gets or sets the Seat's FollowCam property. Tweakable stat.
/// </summary>
public bool FollowCam
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.PilotSeat.SeatFollowCamComponent>(this).followCam;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.PilotSeat.SeatFollowCamComponent>(this).followCam = value;
}
}
/// <summary>
/// Gets or sets the Seat's CharacterColliderHeight property. May not be saved.
/// </summary>
public float CharacterColliderHeight
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.PilotSeat.SeatReadOnlySettingsComponent>(this).characterColliderHeight;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.PilotSeat.SeatReadOnlySettingsComponent>(this).characterColliderHeight = value;
}
}
}
}

View file

@ -1,146 +0,0 @@
namespace TechbloxModdingAPI.Blocks
{
using RobocraftX.Common;
using Svelto.ECS;
public class Servo : SignalingBlock
{
/// <summary>
/// Constructs a(n) Servo object representing an existing block.
/// </summary>
public Servo(EGID egid) :
base(egid)
{
}
/// <summary>
/// Constructs a(n) Servo object representing an existing block.
/// </summary>
public Servo(uint id) :
base(new EGID(id, CommonExclusiveGroups.SERVO_BLOCK_GROUP))
{
}
/// <summary>
/// Gets or sets the Servo's MaximumForce property. Tweakable stat.
/// </summary>
public float MaximumForce
{
get
{
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).servoVelocity;
}
set
{
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).servoVelocity = value;
}
}
/// <summary>
/// Gets or sets the Servo's MinimumAngle property. Tweakable stat.
/// </summary>
public float MinimumAngle
{
get
{
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).minDeviation;
}
set
{
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).minDeviation = value;
}
}
/// <summary>
/// Gets or sets the Servo's MaximumAngle property. Tweakable stat.
/// </summary>
public float MaximumAngle
{
get
{
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).maxDeviation;
}
set
{
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).maxDeviation = value;
}
}
/// <summary>
/// Gets or sets the Servo's Reverse property. Tweakable stat.
/// </summary>
public bool Reverse
{
get
{
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).reverse;
}
set
{
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).reverse = value;
}
}
/// <summary>
/// Gets or sets the Servo's InputIsAngle property. Tweakable stat.
/// </summary>
public bool InputIsAngle
{
get
{
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).hasProportionalInput;
}
set
{
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).hasProportionalInput = value;
}
}
/// <summary>
/// Gets or sets the Servo's DirectionVector property. May not be saved.
/// </summary>
public Unity.Mathematics.float3 DirectionVector
{
get
{
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).directionVector;
}
set
{
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).directionVector = value;
}
}
/// <summary>
/// Gets or sets the Servo's RotationAxis property. May not be saved.
/// </summary>
public Unity.Mathematics.float3 RotationAxis
{
get
{
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).rotationAxis;
}
set
{
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).rotationAxis = value;
}
}
/// <summary>
/// Gets or sets the Servo's ForceAxis property. May not be saved.
/// </summary>
public Unity.Mathematics.float3 ForceAxis
{
get
{
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).forceAxis;
}
set
{
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).forceAxis = value;
}
}
}
}

View file

@ -46,9 +46,9 @@ namespace TechbloxModdingAPI.Blocks
/// <returns>The connected wire.</returns>
/// <param name="portId">Port identifier.</param>
/// <param name="connected">Whether the port has a wire connected to it.</param>
protected OptionalRef<WireEntityStruct> GetConnectedWire(PortEntityStruct port, out EGID egid)
protected ref WireEntityStruct GetConnectedWire(EGID portId, out bool connected)
{
return SignalEngine.MatchPortToWire(port, Id, out egid);
return ref SignalEngine.MatchPortToWire(portId, Id, out connected);
}
/// <summary>
@ -56,9 +56,10 @@ namespace TechbloxModdingAPI.Blocks
/// </summary>
/// <returns>The channel data.</returns>
/// <param name="portId">Port identifier.</param>
protected OptionalRef<ChannelDataStruct> GetChannelData(EGID portId)
/// <param name="exists">Whether the channel actually exists.</param>
protected ref ChannelDataStruct GetChannelData(EGID portId, out bool exists)
{
return SignalEngine.GetChannelDataStruct(portId);
return ref SignalEngine.GetChannelDataStruct(portId, out exists);
}
/// <summary>

View file

@ -1,137 +0,0 @@
using TechbloxModdingAPI.Tests;
namespace TechbloxModdingAPI.Blocks
{
using RobocraftX.Common;
using Svelto.ECS;
public class WheelRig : SignalingBlock
{
/// <summary>
/// Constructs a(n) WheelRig object representing an existing block.
/// </summary>
public WheelRig(EGID egid) :
base(egid)
{
}
/// <summary>
/// Constructs a(n) WheelRig object representing an existing block.
/// </summary>
public WheelRig(uint id) :
base(new EGID(id, CommonExclusiveGroups.WHEELRIG_BLOCK_BUILD_GROUP))
{
}
/// <summary>
/// Gets or sets the WheelRig's BrakingStrength property. Tweakable stat.
/// </summary>
public float BrakingStrength
{
get
{
return BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigTweakableStruct>(this).brakingStrength;
}
set
{
BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigTweakableStruct>(this).brakingStrength = value;
}
}
/// <summary>
/// Gets or sets the WheelRig's FlipDirection property. Tweakable stat.
/// </summary>
public bool FlipDirection
{
get
{
return BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigTweakableStruct>(this).flipDirection;
}
set
{
BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigTweakableStruct>(this).flipDirection = value;
}
}
/// <summary>
/// Gets or sets the WheelRig's MaxVelocity property. May not be saved.
/// </summary>
public float MaxVelocity
{
get
{
return BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigReadOnlyStruct>(this).maxVelocity;
}
set
{
BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigReadOnlyStruct>(this).maxVelocity = value;
}
}
/// <summary>
/// Gets or sets the WheelRig's SteerAngle property. Tweakable stat.
/// </summary>
[TestValue(0f)] // Can be 0 for no steer variant
public float SteerAngle
{
get
{
return BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigSteerableTweakableStruct>(this).steerAngle;
}
set
{
BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigSteerableTweakableStruct>(this).steerAngle = value;
}
}
/// <summary>
/// Gets or sets the WheelRig's FlipSteering property. Tweakable stat.
/// </summary>
[TestValue(false)]
public bool FlipSteering
{
get
{
return BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigSteerableTweakableStruct>(this).flipSteering;
}
set
{
BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigSteerableTweakableStruct>(this).flipSteering = value;
}
}
/// <summary>
/// Gets or sets the WheelRig's VelocityForMinAngle property. May not be saved.
/// </summary>
[TestValue(0f)]
public float VelocityForMinAngle
{
get
{
return BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigSteerableReadOnlyStruct>(this).velocityForMinAngle;
}
set
{
BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigSteerableReadOnlyStruct>(this).velocityForMinAngle = value;
}
}
/// <summary>
/// Gets or sets the WheelRig's MinSteerAngleFactor property. May not be saved.
/// </summary>
[TestValue(0f)]
public float MinSteerAngleFactor
{
get
{
return BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigSteerableReadOnlyStruct>(this).minSteerAngleFactor;
}
set
{
BlockEngine.GetBlockInfo<Techblox.WheelRigBlock.WheelRigSteerableReadOnlyStruct>(this).minSteerAngleFactor = value;
}
}
}
}

View file

@ -5,11 +5,10 @@ using Svelto.ECS;
using Svelto.ECS.Experimental;
using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks
{
public class Wire : EcsObjectBase
public class Wire
{
internal static SignalEngine signalEngine;
@ -31,8 +30,8 @@ namespace TechbloxModdingAPI.Blocks
public static Wire Connect(SignalingBlock start, byte startPort, SignalingBlock end, byte endPort)
{
var (wire, id) = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort);
return new Wire(wire, start, end, id);
WireEntityStruct wire = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort);
return new Wire(wire, start, end);
}
/// <summary>
@ -44,12 +43,14 @@ namespace TechbloxModdingAPI.Blocks
/// <returns>The wire, where the end of the wire is the block port specified, or null if does not exist.</returns>
public static Wire ConnectedToInputPort(SignalingBlock end, byte endPort)
{
var port = signalEngine.MatchBlockIOToPort(end, endPort, false);
if (!port) return null;
var wire = signalEngine.MatchPortToWire(port, end.Id, out var egid);
return wire
? new Wire(wire.Get().sourceBlockEGID, end.Id, wire.Get().sourcePortUsage, endPort, egid, false)
: null;
EGID port = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists);
if (!exists) return null;
WireEntityStruct wire = signalEngine.MatchPortToWire(port, end.Id, out exists);
if (exists)
{
return new Wire(Block.New(wire.sourceBlockEGID), end, wire.sourcePortUsage, endPort);
}
return null;
}
/// <summary>
@ -61,51 +62,63 @@ namespace TechbloxModdingAPI.Blocks
/// <returns>The wire, where the start of the wire is the block port specified, or null if does not exist.</returns>
public static Wire ConnectedToOutputPort(SignalingBlock start, byte startPort)
{
var port = signalEngine.MatchBlockIOToPort(start, startPort, true);
if (!port) return null;
var wire = signalEngine.MatchPortToWire(port, start.Id, out var egid);
return wire
? new Wire(start.Id, wire.Get().destinationBlockEGID, startPort, wire.Get().destinationPortUsage, egid, false)
: null;
EGID port = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists);
if (!exists) return null;
WireEntityStruct wire = signalEngine.MatchPortToWire(port, start.Id, out exists);
if (exists)
{
return new Wire(start, Block.New(wire.destinationBlockEGID), startPort, wire.destinationPortUsage);
}
return null;
}
/// <summary>
/// Construct a wire object froam n existing connection.
/// Construct a wire object from an existing connection.
/// </summary>
/// <param name="start">Starting block ID.</param>
/// <param name="end">Ending block ID.</param>
/// <param name="startPort">Starting port number, or guess if omitted.</param>
/// <param name="endPort">Ending port number, or guess if omitted.</param>
/// <exception cref="WireInvalidException">Guessing failed or wire does not exist.</exception>
public Wire(Block start, Block end, byte startPort = Byte.MaxValue, byte endPort = Byte.MaxValue) : base(ecs =>
public Wire(Block start, Block end, byte startPort = Byte.MaxValue, byte endPort = Byte.MaxValue)
{
var th = (Wire)ecs;
th.startBlockEGID = start.Id;
th.endBlockEGID = end.Id;
bool flipped = false;
startBlockEGID = start.Id;
endBlockEGID = end.Id;
// find block ports
EGID wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, startPort, endPort);
if (wire == default)
WireEntityStruct wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, out bool exists, startPort, endPort);
if (exists)
{
wireEGID = wire.ID;
endPortEGID = signalEngine.MatchBlockInputToPort(end, wire.destinationPortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockOutputToPort(start, wire.sourcePortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
inputToOutput = false;
endPort = wire.destinationPortUsage;
startPort = wire.sourcePortUsage;
}
else
{
// flip I/O around and try again
wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, endPort, startPort);
flipped = true;
wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, out exists, endPort, startPort);
if (exists)
{
wireEGID = wire.ID;
endPortEGID = signalEngine.MatchBlockOutputToPort(end, wire.sourcePortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockInputToPort(start, wire.destinationPortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
inputToOutput = true; // end is actually the source
// NB: start and end are handled exactly as they're received as params.
// This makes wire traversal easier, but makes logic in this class a bit more complex
}
if (wire != default)
{
th.Construct(start.Id, end.Id, startPort, endPort, wire, flipped);
endPort = wire.sourcePortUsage;
startPort = wire.destinationPortUsage;
}
else
{
throw new WireInvalidException("Wire not found");
}
return th.wireEGID;
})
{
}
}
/// <summary>
@ -118,25 +131,25 @@ namespace TechbloxModdingAPI.Blocks
/// <param name="wire">The wire ID.</param>
/// <param name="inputToOutput">Whether the wire direction goes input -> output (true) or output -> input (false, preferred).</param>
public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput)
: this(start.Id, end.Id, startPort, endPort, wire, inputToOutput)
{
}
private Wire(EGID startBlock, EGID endBlock, byte startPort, byte endPort, EGID wire, bool inputToOutput) : base(wire)
{
Construct(startBlock, endBlock, startPort, endPort, wire, inputToOutput);
}
private void Construct(EGID startBlock, EGID endBlock, byte startPort, byte endPort, EGID wire, bool inputToOutput)
{
this.startBlockEGID = startBlock;
this.endBlockEGID = endBlock;
this.startBlockEGID = start.Id;
this.endBlockEGID = end.Id;
this.inputToOutput = inputToOutput;
this.wireEGID = wire;
endPortEGID = signalEngine.MatchBlockIOToPort(startBlock, startPort, inputToOutput).EGID;
if (endPortEGID == default) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockIOToPort(endBlock, endPort, !inputToOutput).EGID;
if (startPortEGID == default) throw new WireInvalidException("Wire start port not found");
if (inputToOutput)
{
endPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
}
else
{
endPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
}
this.startPort = startPort;
this.endPort = endPort;
}
@ -145,16 +158,41 @@ namespace TechbloxModdingAPI.Blocks
/// Construct a wire object from an existing wire connection.
/// </summary>
/// <param name="wireEgid">The wire ID.</param>
public Wire(EGID wireEgid) : base(wireEgid)
public Wire(EGID wireEgid)
{
this.wireEGID = wireEgid;
WireEntityStruct wire = signalEngine.GetWire(wireEGID);
Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage,
wireEgid, false);
this.startBlockEGID = wire.sourceBlockEGID;
this.endBlockEGID = wire.destinationBlockEGID;
this.inputToOutput = false;
endPortEGID = signalEngine.MatchBlockInputToPort(wire.destinationBlockEGID, wire.destinationPortUsage, out bool exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockOutputToPort(wire.sourceBlockEGID, wire.sourcePortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
this.endPort = wire.destinationPortUsage;
this.startPort = wire.sourcePortUsage;
}
private Wire(WireEntityStruct wire, SignalingBlock src, SignalingBlock dest, EGID wireEgid)
: this(src, dest, wire.sourcePortUsage, wire.destinationPortUsage, wireEgid, false)
internal Wire(WireEntityStruct wire, SignalingBlock src, SignalingBlock dest)
{
this.wireEGID = wire.ID;
this.startBlockEGID = wire.sourceBlockEGID;
this.endBlockEGID = wire.destinationBlockEGID;
inputToOutput = false;
endPortEGID = signalEngine.MatchBlockInputToPort(dest, wire.destinationPortUsage, out bool exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockOutputToPort(src, wire.sourcePortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
this.endPort = wire.destinationPortUsage;
this.startPort = wire.sourcePortUsage;
}
/// <summary>
/// The wire's in-game id.
/// </summary>
public EGID Id
{
get => wireEGID;
}
/// <summary>
@ -164,12 +202,16 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsFloat;
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return 0f;
return cds.valueAsFloat;
}
set
{
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsFloat = value;
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return;
cds.valueAsFloat = value;
}
}
@ -180,12 +222,16 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString;
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return "";
return cds.valueAsEcsString;
}
set
{
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString.Set(value);
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return;
cds.valueAsEcsString.Set(value);
}
}
@ -196,12 +242,16 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString;
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return default;
return cds.valueAsEcsString;
}
set
{
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString = value;
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return;
cds.valueAsEcsString = value;
}
}
@ -213,12 +263,16 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsID;
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return uint.MaxValue;
return cds.valueAsID;
}
set
{
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsID = value;
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return;
cds.valueAsID = value;
}
}
@ -227,7 +281,7 @@ namespace TechbloxModdingAPI.Blocks
/// </summary>
public SignalingBlock Start
{
get => (SignalingBlock)Block.New(startBlockEGID);
get => new SignalingBlock(startBlockEGID);
}
/// <summary>
@ -238,20 +292,12 @@ namespace TechbloxModdingAPI.Blocks
get => startPort;
}
/// <summary>
/// The display name of the start port.
/// </summary>
public string StartPortName
{
get => signalEngine.GetPort(startPortEGID).portNameLocalised;
}
/// <summary>
/// The block at the end of the wire.
/// </summary>
public SignalingBlock End
{
get => (SignalingBlock)Block.New(endBlockEGID);
get => new SignalingBlock(endBlockEGID);
}
/// <summary>
@ -262,14 +308,6 @@ namespace TechbloxModdingAPI.Blocks
get => endPort;
}
/// <summary>
/// The display name of the end port.
/// </summary>
public string EndPortName
{
get => signalEngine.GetPort(endPortEGID).portNameLocalised;
}
/// <summary>
/// Create a copy of the wire object where the direction of the wire is guaranteed to be from a block output to a block input.
/// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input).
@ -277,7 +315,7 @@ namespace TechbloxModdingAPI.Blocks
/// <returns>A copy of the wire object.</returns>
public Wire OutputToInputCopy()
{
return GetInstance(wireEGID, egid => new Wire(egid));
return new Wire(wireEGID);
}
/// <summary>
@ -291,11 +329,15 @@ namespace TechbloxModdingAPI.Blocks
{
inputToOutput = false;
// swap inputs and outputs
(endBlockEGID, startBlockEGID) = (startBlockEGID, endBlockEGID);
var tempPort = endPortEGID;
EGID temp = endBlockEGID;
endBlockEGID = startBlockEGID;
startBlockEGID = temp;
temp = endPortEGID;
endPortEGID = startPortEGID;
startPortEGID = tempPort;
(endPort, startPort) = (startPort, endPort);
startPortEGID = temp;
byte tempPortNumber = endPort;
endPort = startPort;
startPort = tempPortNumber;
}
}
@ -303,7 +345,7 @@ namespace TechbloxModdingAPI.Blocks
{
if (signalEngine.Exists<WireEntityStruct>(wireEGID))
{
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} aka {(StartPort != byte.MaxValue ? Start.PortName(StartPort, inputToOutput) : "")}) -> ({End.Type}::{EndPort} aka {(EndPort != byte.MaxValue ? End.PortName(EndPort, !inputToOutput) : "")})";
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} aka {Start.PortName(StartPort, inputToOutput)}) -> ({End.Type}::{EndPort} aka {End.PortName(EndPort, !inputToOutput)})";
}
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} -> {End.Type}::{EndPort})";
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using Unity.Mathematics;
using UnityEngine;

View file

@ -1,5 +1,6 @@
using Svelto.ECS;
using Techblox.TimeRunning.Clusters;
using Gamecraft.Damage;
using RobocraftX.Common;
using Svelto.ECS;
namespace TechbloxModdingAPI
{
@ -9,37 +10,35 @@ namespace TechbloxModdingAPI
/// </summary>
public class Cluster : EcsObjectBase
{
public Cluster(EGID id) : base(id)
public override EGID Id { get; }
public Cluster(EGID id)
{
Id = id;
}
public Cluster(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_CLUSTERS_GROUP))
{
}
public Cluster(uint id) : this(new EGID(id, ClustersExclusiveGroups.SIMULATION_CLUSTERS_GROUP))
public float InitialHealth
{
}
public float InitialHealth //TODO
{
get => 0f;
set { }
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth;
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth = value;
}
public float CurrentHealth
{
get => 0f;
set { }
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth;
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth = value;
}
public float HealthMultiplier
{
get => 0f;
set { }
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier;
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier = value;
}
/// <summary>
/// The mass of the cluster.
/// </summary>
public float Mass => Block.BlockEngine.GetBlockInfo<ClusterMassComponent>(this).mass;
/// <summary>
/// Returns the simulation-time rigid bodies for the chunks in this cluster.
/// </summary>

View file

@ -0,0 +1,107 @@
using System;
using System.Reflection;
using HarmonyLib;
using Svelto.ECS;
using RobocraftX.CR.MainGame;
using RobocraftX.Multiplayer;
using RobocraftX.StateSync;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Commands
{
/// <summary>
/// Patch of RobocraftX.CR.MainGame.MainGameCompositionRoot.DeterministicCompose<T>()
/// Initializes existing and custom commands
/// </summary>
[HarmonyPatch]
static class CommandPatch
{
public static void Postfix(Action reloadGame, MultiplayerInitParameters multiplayerParameters,
StateSyncRegistrationHelper stateSyncReg)
{
/*CommandLineCompositionRoot.Compose(contextHolder, stateSyncReg.enginesRoot, reloadGame, multiplayerParameters,
stateSyncReg); - uREPL C# compilation not supported anymore */
var enginesRoot = stateSyncReg.enginesRoot;
var entityFunctions = enginesRoot.GenerateEntityFunctions();
var entityFactory = enginesRoot.GenerateEntityFactory();
var entitySerializer = enginesRoot.GenerateEntitySerializer();
Logging.MetaDebugLog("Adding existing command engines");
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ExecuteSetGravityCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ExecuteSetPhysicsPrecisionCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ExecuteSetPhysicsFrequencyCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName(
"RobocraftX.GUI.CommandLine.ExecuteClearAllPartsCommandEngine"),
entityFunctions));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ExecuteHelpCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName(
"RobocraftX.GUI.CommandLine.ExecuteSetLinearRestingThresholdCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName(
"RobocraftX.GUI.CommandLine.ExecuteSetAngularRestingThresholdCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ExecuteEnableVisualProfilerCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ExecuteSetNetworkJitterFramesEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ExecuteSetSendConnectedEntitiesCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ExecuteSetMaxSimFramesEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.SetDebugDisplayExtraInfoCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.SetNetSyncBandwidthLimitCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ThrowExceptionCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.SetPriorityCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.TeleportCharacterCommandEngine"),
entityFactory));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ChangeTextBlockTextCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.SetCharacterRunSpeedCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.SetCameraZoomDistanceCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.EditLightingSettingsCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.EditSkySettingsCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.EditFogSettingsCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.TeleportCharacterImplementationEngine"),
entityFunctions));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ExecuteConnectToServerCommandEngine"),
entityFunctions, entitySerializer, reloadGame, multiplayerParameters));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.SetInputBroadcastCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ExecuteSetJointInertiaTensorCommandEngine")));
enginesRoot.AddEngine(
(IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.ChangeTeamCommandEngine")));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.DamageCharacterCommandEngine"), entityFactory));
enginesRoot.AddEngine((IEngine) Activator.CreateInstance(
AccessTools.TypeByName("RobocraftX.GUI.CommandLine.DisableCharacterDamageCommandEngine")));
Logging.MetaDebugLog("Existing command engines added");
CommandManager.RegisterEngines(enginesRoot);
}
public static MethodInfo TargetMethod()
{
return AccessTools.Method(typeof(MainGameCompositionRoot), "DeterministicCompose")
.MakeGenericMethod(typeof(object));
}
}
}

View file

@ -4,6 +4,9 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using uREPL;
using RobocraftX.CommandLine.Custom;
namespace TechbloxModdingAPI.Commands
{
/// <summary>
@ -14,7 +17,9 @@ namespace TechbloxModdingAPI.Commands
{
public static void Register(string name, Action action, string desc, bool noConsole = false)
{
CustomCommands.Register(name, action, desc);
RuntimeCommands.Register(name, action, desc);
if (noConsole) { return; }
ConsoleCommands.Register(name, action, desc);
}
public static void Register(string name, Action<object> action, string desc, bool noConsole = false)
@ -34,42 +39,50 @@ namespace TechbloxModdingAPI.Commands
public static void Register<Param0>(string name, Action<Param0> action, string desc, bool noConsole = false)
{
CustomCommands.Register(name, action, desc);
RuntimeCommands.Register<Param0>(name, action, desc);
if (noConsole) { return; }
ConsoleCommands.Register<Param0>(name, action, desc);
}
public static void Register<Param0, Param1>(string name, Action<Param0, Param1> action, string desc, bool noConsole = false)
{
CustomCommands.Register(name, action, desc);
RuntimeCommands.Register<Param0, Param1>(name, action, desc);
if (noConsole) { return; }
ConsoleCommands.Register<Param0, Param1>(name, action, desc);
}
public static void Register<Param0, Param1, Param2>(string name, Action<Param0, Param1, Param2> action, string desc, bool noConsole = false)
{
CustomCommands.Register(name, action, desc);
RuntimeCommands.Register<Param0, Param1, Param2>(name, action, desc);
if (noConsole) { return; }
ConsoleCommands.Register<Param0, Param1, Param2>(name, action, desc);
}
public static void Unregister(string name, bool noConsole = false)
{
CustomCommands.Unregister(name);
RuntimeCommands.Unregister(name);
if (noConsole) { return; }
ConsoleCommands.Unregister(name);
}
public static void Call(string name)
{
CustomCommands.Call(name);
RuntimeCommands.Call(name);
}
public static void Call<Param0>(string name, Param0 param0)
{
CustomCommands.Call(name, param0);
RuntimeCommands.Call<Param0>(name, param0);
}
public static void Call<Param0, Param1>(string name, Param0 param0, Param1 param1)
{
CustomCommands.Call(name, param0, param1);
RuntimeCommands.Call<Param0, Param1>(name, param0, param1);
}
public static void Call<Param0, Param1, Param2>(string name, Param0 param0, Param1 param1, Param2 param2)
{
CustomCommands.Call(name, param0, param1, param2);
RuntimeCommands.Call<Param0, Param1, Param2>(name, param0, param1, param2);
}
}
}

View file

@ -1,57 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
namespace TechbloxModdingAPI.Commands
{
internal static class CustomCommands
{
public struct CommandData
{
public string Name;
public string Description;
public Delegate Action;
}
private static Dictionary<string, CommandData> _commands = new Dictionary<string, CommandData>();
public static void Register(string name, Delegate action, string desc)
{
_commands.Add(name, new CommandData
{
Name = name,
Description = desc,
Action = action
});
}
public static void Call(string name, params object[] args)
{
if (_commands.TryGetValue(name, out var command))
{
var paramz = command.Action.Method.GetParameters();
if (paramz.Length > args.Length)
throw new CommandParameterMissingException(
$"This command requires {paramz.Length} arguments, {args.Length} given");
for (var index = 0; index < paramz.Length; index++)
{
args[index] = Convert.ChangeType(args[index], paramz[index].ParameterType);
}
command.Action.DynamicInvoke(args);
}
else
throw new CommandNotFoundException($"Command {name} does not exist!");
}
public static void Unregister(string name)
{
_commands.Remove(name);
}
public static bool Exists(string name) => _commands.ContainsKey(name);
public static ReadOnlyDictionary<string, CommandData> GetAllCommandData() =>
new ReadOnlyDictionary<string, CommandData>(_commands);
}
}

View file

@ -1,37 +1,39 @@
using System.Linq;
using uREPL;
namespace TechbloxModdingAPI.Commands
{
public static class ExistingCommands
{
public static void Call(string commandName)
{
CustomCommands.Call(commandName);
RuntimeCommands.Call(commandName);
}
public static void Call<Arg0>(string commandName, Arg0 arg0)
{
CustomCommands.Call(commandName, arg0);
RuntimeCommands.Call<Arg0>(commandName, arg0);
}
public static void Call<Arg0, Arg1>(string commandName, Arg0 arg0, Arg1 arg1)
{
CustomCommands.Call(commandName, arg0, arg1);
RuntimeCommands.Call<Arg0, Arg1>(commandName, arg0, arg1);
}
public static void Call<Arg0, Arg1, Arg2>(string commandName, Arg0 arg0, Arg1 arg1, Arg2 arg2)
{
CustomCommands.Call(commandName, arg0, arg1, arg2);
RuntimeCommands.Call<Arg0, Arg1, Arg2>(commandName, arg0, arg1, arg2);
}
public static bool Exists(string commandName)
{
return CustomCommands.Exists(commandName);
return RuntimeCommands.HasRegistered(commandName);
}
public static (string Name, string Description)[] GetCommandNamesAndDescriptions()
{
return CustomCommands.GetAllCommandData().Values.Select(command => (command.Name, command.Description)).ToArray();
return RuntimeCommands.table.Values.Select(command => (command.name, command.description)).ToArray();
}
}
}

View file

@ -1,78 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.Internal;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Blocks;
namespace TechbloxModdingAPI
{
public abstract class EcsObjectBase
{
public EGID Id { get; }
private static readonly Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>> _instances =
new Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>>();
private static readonly WeakDictionary<EGID, EcsObjectBase> _noInstance =
new WeakDictionary<EGID, EcsObjectBase>();
internal static WeakDictionary<EGID, EcsObjectBase> GetInstances(Type type)
{
return _instances.TryGetValue(type, out var dict) ? dict : null;
}
/// <summary>
/// Returns a cached instance if there's an actively used instance of the object already.
/// Objects still get garbage collected and then they will be removed from the cache.
/// </summary>
/// <param name="egid">The EGID of the entity</param>
/// <param name="constructor">The constructor to construct the object</param>
/// <typeparam name="T">The object type</typeparam>
/// <returns></returns>
internal static T GetInstance<T>(EGID egid, Func<EGID, T> constructor, Type type = null) where T : EcsObjectBase
{
var instances = GetInstances(type ?? typeof(T));
if (instances == null || !instances.TryGetValue(egid, out var instance))
return constructor(egid); // It will be added by the constructor
return (T)instance;
}
protected EcsObjectBase(EGID id)
{
if (!_instances.TryGetValue(GetType(), out var dict))
{
dict = new WeakDictionary<EGID, EcsObjectBase>();
_instances.Add(GetType(), dict);
}
if (!dict.ContainsKey(id)) // Multiple instances may be created
dict.Add(id, this);
Id = id;
}
protected EcsObjectBase(Func<EcsObjectBase, EGID> initializer)
{
if (!_instances.TryGetValue(GetType(), out var dict))
{
dict = new WeakDictionary<EGID, EcsObjectBase>();
_instances.Add(GetType(), dict);
}
var id = initializer(this);
if (!dict.ContainsKey(id)) // Multiple instances may be created
dict.Add(id, this);
else
{
Logging.MetaDebugLog($"An object of this type and ID is already stored: {GetType()} - {id}");
Logging.MetaDebugLog(this);
Logging.MetaDebugLog(dict[id]);
}
Id = id;
}
#region ECS initializer stuff
public abstract EGID Id { get; } //Abstract to support the 'place' Block constructor
protected internal EcsInitData InitData;
@ -122,7 +59,5 @@ namespace TechbloxModdingAPI
Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] {objParam});
return lambda.Compile();
}
#endregion
}
}

View file

@ -5,68 +5,25 @@ using RobocraftX.CR.MainGame;
using RobocraftX.FrontEnd;
using RobocraftX.StateSync;
using Svelto.ECS;
using Svelto.ECS.Schedulers;
using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Engines
{
[HarmonyPatch]
static class GameLoadedTimeStoppedEnginePatch
class GameLoadedEnginePatch
{
public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{
// register all game engines, including deterministic
GameEngineManager.RegisterEngines(stateSyncReg);
// register command engines
/*CommandLineCompositionRoot.Compose(contextHolder, stateSyncReg.enginesRoot, reloadGame, multiplayerParameters,
stateSyncReg); - uREPL C# compilation not supported anymore */
CommandManager.RegisterEngines(stateSyncReg.enginesRoot);
}
public static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(MainGameCompositionRoot), "DeterministicTimeStoppedCompose").MakeGenericMethod(typeof(object));
return AccessTools.Method(typeof(MainGameCompositionRoot), "DeterministicCompose").MakeGenericMethod(typeof(object));
}
}
[HarmonyPatch]
static class GameLoadedTimeRunningEnginePatch
{
public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{
GameEngineManager.RegisterEngines(stateSyncReg);
CommandManager.RegisterEngines(stateSyncReg.enginesRoot);
}
public static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(MainGameCompositionRoot), "DeterministicTimeRunningCompose").MakeGenericMethod(typeof(object));
}
}
[HarmonyPatch]
static class GameReloadedPatch
{
internal static bool IsReload;
public static void Prefix() => IsReload = true;
public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "ReloadGame");
}
[HarmonyPatch]
static class GameSwitchedToPatch
{
public static void Prefix() => GameReloadedPatch.IsReload = false;
public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame");
}
[HarmonyPatch]
static class MenuSwitchedToPatch
{
public static void Prefix() => GameReloadedPatch.IsReload = false;
public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToMenu");
}
[HarmonyPatch]
class MenuLoadedEnginePatch
{

View file

@ -1,9 +0,0 @@
using Svelto.ECS;
namespace TechbloxModdingAPI.Engines
{
public interface IFunEngine : IApiEngine
{
public IEntityFunctions Functions { set; }
}
}

View file

@ -14,7 +14,7 @@ namespace TechbloxModdingAPI.Engines
/// <summary>
/// Engine interface to handle ModEventEntityStruct events emitted by IEventEmitterEngines.
/// </summary>
public interface IReactionaryEngine<T> : IApiEngine, IReactOnAddAndRemove<T> where T : unmanaged, IEntityComponent
public interface IReactionaryEngine<T> : IApiEngine, IReactOnAddAndRemove<T>, IReactOnAddAndRemove where T : unmanaged, IEntityComponent
{
}
}

View file

@ -1,19 +1,21 @@
using RobocraftX.Common.Input;
using System;
using TechbloxModdingAPI.App;
using RobocraftX.Common;
using RobocraftX.Common.Input;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Input
{
public static class FakeInput
{
internal static readonly FakeInputEngine inputEngine = new FakeInputEngine();
private static readonly FakeInputEngine inputEngine = new FakeInputEngine();
/// <summary>
/// Customize the local input.
/// </summary>
/// <param name="input">The custom input.</param>
public static void CustomInput(LocalCosmeticInputEntityComponent input)
public static void CustomInput(LocalInputEntityStruct input)
{
inputEngine.SendCustomInput(input);
}
@ -32,7 +34,7 @@ namespace TechbloxModdingAPI.Input
inputEngine.SendCustomPlayerInput(input, playerID);
}
public static LocalCosmeticInputEntityComponent GetInput()
public static LocalInputEntityStruct GetInput()
{
return inputEngine.GetInput();
}
@ -51,18 +53,26 @@ namespace TechbloxModdingAPI.Input
/// Omit any parameter you do not want to affect.
/// Parameters that end with "?" don't do anything... yet.
/// </summary>
/// <param name="playerID">The player. Omit this to use the local player.</param>
/// <param name="hotbar">Select the hotbar slot by number.</param>
/// <param name="commandLine">Toggle the command line?</param>
/// <param name="escape">Open escape menu?</param>
/// <param name="enter">Page return?</param>
/// <param name="debug">Toggle debug display?</param>
/// <param name="next">Select next?</param>
/// <param name="previous">Select previous?</param>
/// <param name="tab">Tab?</param>
/// <param name="colour">Toggle to hotbar colour mode?</param>
/// <param name="hotbarPage">Select the hotbar page by number?</param>
/// <param name="quickSave">Quicksave?</param>
/// <param name="paste">Paste?</param>
public static void GuiInput(int hotbar = -1, bool escape = false, bool enter = false, bool debug = false, int hotbarPage = -1, bool quickSave = false, bool paste = false)
public static void GuiInput(uint playerID = uint.MaxValue, int hotbar = -1, bool escape = false, bool enter = false, bool debug = false, bool next = false, bool previous = false, bool tab = false, int hotbarPage = -1, bool quickSave = false, bool paste = false)
{
ref LocalCosmeticInputEntityComponent currentInput = ref inputEngine.GetInputRef();
if (playerID == uint.MaxValue)
{
playerID = inputEngine.GetLocalPlayerID();
}
ref LocalInputEntityStruct currentInput = ref inputEngine.GetInputRef();
//Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}");
// set inputs
switch(hotbar)
@ -82,6 +92,9 @@ namespace TechbloxModdingAPI.Input
if (escape) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.Escape;
if (enter) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.Return;
if (debug) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.ToggleDebugDisplay;
if (next) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.SelectNext;
if (previous) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.SelectPrev;
if (tab) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.Tab;
switch (hotbarPage)
{
case 1: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage1; break;
@ -94,46 +107,47 @@ namespace TechbloxModdingAPI.Input
case 8: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage8; break;
case 9: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage9; break;
case 10: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage10; break;
default: break;
}
//RewiredConsts.Action
if (quickSave) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.QuickSave;
if (paste) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.SelectLastCopiedBlueprint;
if (paste) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.PasteSelection;
}
public static void ActionInput(uint playerID = uint.MaxValue, bool toggleMode = false, bool forward = false, bool backward = false, bool up = false, bool down = false, bool left = false, bool right = false, bool sprint = false, bool toggleFly = false, bool alt = false, bool primary = false, bool secondary = false, bool tertiary = false, bool primaryHeld = false, bool secondaryHeld = false, bool toggleUnitGrid = false, bool ctrl = false, bool toggleColourMode = false, bool scaleBlockUp = false, bool scaleBlockDown = false, bool rotateBlockClockwise = false, bool rotateBlockCounterclockwise = false, bool cutSelection = false, bool copySelection = false, bool deleteSelection = false)
{ // TODO: We can only alter our own inputs clientside
ref var currentInput = ref inputEngine._localInputCache;
{
if (playerID == uint.MaxValue)
{
playerID = inputEngine.GetLocalPlayerID();
}
ref LocalPlayerInputEntityStruct currentInput = ref inputEngine.GetPlayerInputRef(playerID);
//Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}");
// set inputs
if (toggleMode) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningModePlay; //TODO: Test, play
if (forward) currentInput |= RobocraftX.Common.Input.ActionInput.Forward;
if (backward) currentInput |= RobocraftX.Common.Input.ActionInput.Backward;
if (up) currentInput |= RobocraftX.Common.Input.ActionInput.Up;
if (down) currentInput |= RobocraftX.Common.Input.ActionInput.Down;
if (left) currentInput |= RobocraftX.Common.Input.ActionInput.Left;
if (right) currentInput |= RobocraftX.Common.Input.ActionInput.Right;
if (sprint) currentInput |= RobocraftX.Common.Input.ActionInput.Sprint;
//if (toggleFly) currentInput |= RobocraftX.Common.Input.ActionInput.SwitchFlyMode;
//if (alt) currentInput |= RobocraftX.Common.Input.ActionInput.AltAction;
if (primary) currentInput |= RobocraftX.Common.Input.ActionInput.PrimaryActionClick;
if (secondary) currentInput |= RobocraftX.Common.Input.ActionInput.SecondaryActionClick;
if (tertiary) currentInput |= RobocraftX.Common.Input.ActionInput.TertiaryAction;
if (primaryHeld) currentInput |= RobocraftX.Common.Input.ActionInput.PrimaryActionHeld;
if (secondaryHeld) currentInput |= RobocraftX.Common.Input.ActionInput.SecondaryActionHeld;
//if (toggleUnitGrid) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleUnitGrid;
if (ctrl) currentInput |= RobocraftX.Common.Input.ActionInput.CtrlAction;
if (toggleColourMode) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleColourMode;
if (scaleBlockUp) currentInput |= RobocraftX.Common.Input.ActionInput.ScaleBlockUp;
if (scaleBlockDown) currentInput |= RobocraftX.Common.Input.ActionInput.ScaleBlockDown;
if (rotateBlockClockwise) currentInput |= RobocraftX.Common.Input.ActionInput.RotateBlockClockwise;
if (rotateBlockCounterclockwise) currentInput |= RobocraftX.Common.Input.ActionInput.RotateBlockAnticlockwise;
if (cutSelection) currentInput |= RobocraftX.Common.Input.ActionInput.CutSelection;
if (copySelection) currentInput |= RobocraftX.Common.Input.ActionInput.CopySelection;
if (deleteSelection) currentInput |= RobocraftX.Common.Input.ActionInput.DeleteSelection;
if(Game.CurrentGame().IsTimeStopped)
inputEngine.HandleCustomInput(); // Only gets called when online, so calling it here as well
if (toggleMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningMode;
if (forward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Forward;
if (backward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Backward;
if (up) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Up;
if (down) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Down;
if (left) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Left;
if (right) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Right;
if (sprint) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Sprint;
//if (toggleFly) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SwitchFlyMode;
//if (alt) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.AltAction;
if (primary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.PrimaryAction;
if (secondary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SecondaryAction;
if (tertiary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.TertiaryAction;
if (primaryHeld) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.PrimaryActionHeld;
if (secondaryHeld) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SecondaryActionHeld;
//if (toggleUnitGrid) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleUnitGrid;
if (ctrl) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CtrlAction;
if (toggleColourMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleColourMode;
if (scaleBlockUp) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ScaleBlockUp;
if (scaleBlockDown) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ScaleBlockDown;
if (rotateBlockClockwise) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.RotateBlockClockwise;
if (rotateBlockCounterclockwise) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.RotateBlockAnticlockwise;
if (cutSelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CutSelection;
if (copySelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CopySelection;
if (deleteSelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.DeleteSelection;
}
public static void Init()

View file

@ -20,8 +20,6 @@ namespace TechbloxModdingAPI.Input
public bool IsReady = false;
internal ActionInput _localInputCache;
public void Dispose()
{
IsReady = false;
@ -32,12 +30,12 @@ namespace TechbloxModdingAPI.Input
IsReady = true;
}
public bool SendCustomInput(LocalCosmeticInputEntityComponent input)
public bool SendCustomInput(LocalInputEntityStruct input)
{
EGID egid = CommonExclusiveGroups.GameStateEGID;
if (entitiesDB.Exists<LocalCosmeticInputEntityComponent>(egid))
if (entitiesDB.Exists<LocalInputEntityStruct>(egid))
{
ref LocalCosmeticInputEntityComponent ies = ref entitiesDB.QueryEntity<LocalCosmeticInputEntityComponent>(egid);
ref LocalInputEntityStruct ies = ref entitiesDB.QueryEntity<LocalInputEntityStruct>(egid);
ies = input;
return true;
}
@ -56,14 +54,14 @@ namespace TechbloxModdingAPI.Input
else return false;
}
public LocalCosmeticInputEntityComponent GetInput()
public LocalInputEntityStruct GetInput()
{
EGID egid = CommonExclusiveGroups.GameStateEGID;
if (entitiesDB.Exists<LocalCosmeticInputEntityComponent>(egid))
if (entitiesDB.Exists<LocalInputEntityStruct>(egid))
{
return entitiesDB.QueryEntity<LocalCosmeticInputEntityComponent>(egid);
return entitiesDB.QueryEntity<LocalInputEntityStruct>(egid);
}
else return default(LocalCosmeticInputEntityComponent);
else return default(LocalInputEntityStruct);
}
public LocalPlayerInputEntityStruct GetPlayerInput(uint playerID, bool remote = false)
@ -76,10 +74,10 @@ namespace TechbloxModdingAPI.Input
else return default;
}
public ref LocalCosmeticInputEntityComponent GetInputRef()
public ref LocalInputEntityStruct GetInputRef()
{
EGID egid = CommonExclusiveGroups.GameStateEGID;
return ref entitiesDB.QueryEntity<LocalCosmeticInputEntityComponent>(egid);
return ref entitiesDB.QueryEntity<LocalInputEntityStruct>(egid);
}
public ref LocalPlayerInputEntityStruct GetPlayerInputRef(uint playerID, bool remote = false)
@ -88,14 +86,6 @@ namespace TechbloxModdingAPI.Input
return ref entitiesDB.QueryEntity<LocalPlayerInputEntityStruct>(egid);
}
internal void HandleCustomInput()
{
if (!LocalPlayerIDUtility.DoesLocalPlayerExist(entitiesDB))
return;
GetPlayerInputRef(GetLocalPlayerID()).actionMask |= _localInputCache;
_localInputCache = default;
}
public uint GetLocalPlayerID()
{
return LocalPlayerIDUtility.GetLocalPlayerID(entitiesDB);

View file

@ -1,19 +0,0 @@
using System.Reflection;
using HarmonyLib;
namespace TechbloxModdingAPI.Input
{
[HarmonyPatch]
public static class FakeInputPatch
{
public static void Prefix()
{
FakeInput.inputEngine.HandleCustomInput(); // This gets called right before the input is sent to the server
}
public static MethodBase TargetMethod()
{
return AccessTools.Method("RobocraftX.Multiplayer.Input.DeterministicInputRecorderEngine:RecordDeterministicInput");
}
}
}

View file

@ -24,7 +24,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary>
public event EventHandler<bool> OnClick;
public override void OnGUI()
public void OnGUI()
{
if (automaticLayout)
{
@ -42,15 +42,34 @@ namespace TechbloxModdingAPI.Interface.IMGUI
}
}
/// <summary>
/// The button's unique name.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Whether to display the button.
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Initialize a new button with automatic layout.
/// </summary>
/// <param name="text">The text to display on the button.</param>
/// <param name="name">The button's name.</param>
public Button(string text, string name = null) : base(text, name)
public Button(string text, string name = null)
{
automaticLayout = true;
this.text = text;
if (name == null)
{
this.Name = typeof(Button).FullName + "::" + text;
}
else
{
this.Name = name;
}
IMGUIManager.AddElement(this);
}
/// <summary>
@ -64,5 +83,10 @@ namespace TechbloxModdingAPI.Interface.IMGUI
automaticLayout = false;
this.Box = box;
}
~Button()
{
IMGUIManager.RemoveElement(this);
}
}
}

View file

@ -20,12 +20,12 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary>
public Rect Box { get; set; }
public override void OnGUI()
public void OnGUI()
{
/*if (Constants.Default == null) return;
if (Constants.Default.box == null) return;*/
GUIStyle guiStyle = Constants.Default.box;
UIElement[] elems = elements.ToArrayFast(out int count);
UIElement[] elems = elements.ToArrayFast(out uint count);
if (automaticLayout)
{
GUILayout.BeginArea(Box, guiStyle);
@ -61,6 +61,16 @@ namespace TechbloxModdingAPI.Interface.IMGUI
}
}
/// <summary>
/// The group's unique name.
/// </summary>
public string Name { get; }
/// <summary>
/// Whether to display the group and everything in it.
/// </summary>
public bool Enabled { set; get; } = true;
/// <summary>
/// The amount of elements in the group.
/// </summary>
@ -75,10 +85,19 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// <param name="box">The rectangular area to use in the window.</param>
/// <param name="name">Name of the group.</param>
/// <param name="automaticLayout">Whether to use automatic UI layout.</param>
public Group(Rect box, string name = null, bool automaticLayout = false) : base(box.ToString().Replace(" ", ""), name)
public Group(Rect box, string name = null, bool automaticLayout = false)
{
Box = box;
if (name == null)
{
this.Name = typeof(Group).FullName + "::" + box.ToString().Replace(" ", "");
}
else
{
this.Name = name;
}
this.automaticLayout = automaticLayout;
IMGUIManager.AddElement(this);
}
/// <summary>
@ -113,7 +132,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
{
if (index < 0 || index >= elements.count) return false;
IMGUIManager.AddElement(elements[index]); // re-add to global manager
elements.RemoveAt((uint) index);
elements.RemoveAt(index);
return true;
}
@ -124,7 +143,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// <returns>The element's index, or -1 if not found.</returns>
public int IndexOf(UIElement element)
{
UIElement[] elems = elements.ToArrayFast(out int count);
UIElement[] elems = elements.ToArrayFast(out uint count);
for (int i = 0; i < count; i++)
{
if (elems[i].Name == element.Name)

View file

@ -1,8 +1,13 @@
using System;
using System.Collections;
using System.Collections.Generic;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Utility;
using Rewired.Internal;
using Svelto.DataStructures;
using Svelto.Tasks;
using Svelto.Tasks.ExtraLean;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility;
using Svelto.Tasks.ExtraLean.Unity;
using UnityEngine;
namespace TechbloxModdingAPI.Interface.IMGUI
@ -15,9 +20,9 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary>
public static class IMGUIManager
{
internal static OnGuiRunner ImguiScheduler = new("TechbloxModdingAPI_IMGUIScheduler");
internal static OnGuiRunner ImguiScheduler = new OnGuiRunner("TechbloxModdingAPI_IMGUIScheduler");
private static readonly WeakDictionary<string, UIElement> _activeElements = new();
private static Dictionary<string, UIElement> _activeElements = new Dictionary<string,UIElement>();
/// <summary>
/// Add an UIElement instance to be managed by IMGUIManager.

View file

@ -20,7 +20,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary>
public Rect Box { get; set; } = Rect.zero;
public override void OnGUI()
public void OnGUI()
{
//if (Texture == null) return;
if (automaticLayout)
@ -33,15 +33,42 @@ namespace TechbloxModdingAPI.Interface.IMGUI
}
}
/// <summary>
/// The image element's unique name.
/// </summary>
public string Name { get; }
/// <summary>
/// Whether to display the image and everything in it.
/// </summary>
public bool Enabled { set; get; } = true;
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Interface.IMGUI.Image"/> class with automatic layout.
/// </summary>
/// <param name="texture">Image to display.</param>
/// <param name="name">The element's name.</param>
public Image(Texture texture = null, string name = null) : base(texture == null ? "" : $"{texture.name}({texture.width}x{texture.height})", name)
public Image(Texture texture = null, string name = null)
{
automaticLayout = true;
Texture = texture;
if (name == null)
{
if (texture == null)
{
this.Name = typeof(Image).FullName + "::" + texture;
}
else
{
this.Name = typeof(Image).FullName + "::" + texture.name + "(" + texture.width + "x" + texture.height + ")";
}
}
else
{
this.Name = name;
}
IMGUIManager.AddElement(this);
}
/// <summary>

View file

@ -20,7 +20,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary>
public Rect Box { get; set; } = Rect.zero;
public override void OnGUI()
public void OnGUI()
{
if (automaticLayout)
{
@ -32,15 +32,34 @@ namespace TechbloxModdingAPI.Interface.IMGUI
}
}
/// <summary>
/// The label's unique name.
/// </summary>
public string Name { get; }
/// <summary>
/// Whether to display the label.
/// </summary>
public bool Enabled { set; get; } = true;
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Interface.IMGUI.Label"/> class with automatic layout.
/// </summary>
/// <param name="initialText">Initial string to display on the label.</param>
/// <param name="name">The element's name.</param>
public Label(string initialText = null, string name = null) : base(initialText, name)
public Label(string initialText = null, string name = null)
{
automaticLayout = true;
Text = initialText;
if (name == null)
{
this.Name = typeof(Label).FullName + "::" + initialText;
}
else
{
this.Name = name;
}
IMGUIManager.AddElement(this);
}
/// <summary>

View file

@ -28,7 +28,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary>
public event EventHandler<string> OnEdit;
public override void OnGUI()
public void OnGUI()
{
string editedText = null;
if (automaticLayout)
@ -61,17 +61,36 @@ namespace TechbloxModdingAPI.Interface.IMGUI
}
}
/// <summary>
/// The text field's unique name.
/// </summary>
public string Name { get; }
/// <summary>
/// Whether to display the text field.
/// </summary>
public bool Enabled { set; get; } = true;
/// <summary>
/// Initialize the text input field with automatic layout.
/// </summary>
/// <param name="initialText">Initial text in the input field.</param>
/// <param name="name">The text field's name.</param>
/// <param name="multiline">Allow multiple lines?</param>
public Text(string initialText = null, string name = null, bool multiline = false) : base(initialText, name)
public Text(string initialText = null, string name = null, bool multiline = false)
{
this.Multiline = multiline;
automaticLayout = true;
text = initialText ?? "";
if (name == null)
{
this.Name = typeof(Text).FullName + "::" + text;
}
else
{
this.Name = name;
}
IMGUIManager.AddElement(this);
}
/// <summary>

View file

@ -6,34 +6,23 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// GUI Element like a text field, button or picture.
/// This interface is used to wrap many elements from Unity's IMGUI system.
/// </summary>
public abstract class UIElement
public interface UIElement
{
protected UIElement(string text, string name)
{
Name = name ?? GetType().FullName + "::" + text;
IMGUIManager.AddElement(this);
}
~UIElement()
{
IMGUIManager.RemoveElement(this);
}
/// <summary>
/// GUI operations to perform in the OnGUI scope.
/// This is basically equivalent to a MonoBehaviour's OnGUI method.
/// </summary>
public abstract void OnGUI();
void OnGUI();
/// <summary>
/// The element's name.
/// This should be unique for every instance of the class.
/// </summary>
public string Name { get; }
string Name { get; }
/// <summary>
/// Whether to display the UI element or not.
/// </summary>
public bool Enabled { get; set; } = true;
bool Enabled { get; }
}
}

View file

@ -3,11 +3,13 @@ using System.Reflection;
using HarmonyLib;
using RobocraftX;
using RobocraftX.Schedulers;
using RobocraftX.Services;
using Svelto.Context;
using Svelto.Tasks.ExtraLean;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Events;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility;
@ -49,13 +51,15 @@ namespace TechbloxModdingAPI
harmony.PatchAll(currentAssembly);
}
catch (Exception e)
{
HandleError(e, "Failed to patch Techblox. Attempting to patch to display error...", OnPatchError);
{ //Can't use ErrorBuilder or Logging.LogException (which eventually uses ErrorBuilder) yet
Logging.Log(e.ToString());
Logging.LogWarning("Failed to patch Techblox. Attempting to patch to display error...");
harmony.Patch(AccessTools.Method(typeof(FullGameCompositionRoot), "OnContextInitialized")
.MakeGenericMethod(typeof(UnityContext<FullGameCompositionRoot>)),
new HarmonyMethod(((Action) OnPatchError).Method)); //Can't use lambdas here :(
return;
}
try
{
// init utility
Logging.MetaDebugLog($"Initializing Utility");
Utility.GameState.Init();
@ -68,24 +72,14 @@ namespace TechbloxModdingAPI
Block.Init();
BlockGroup.Init();
Wire.Init();
// init client
Logging.MetaDebugLog($"Initializing Client");
Client.Init();
Game.Init();
// init UI
Logging.MetaDebugLog($"Initializing UI");
Interface.IMGUI.Constants.Init();
Interface.IMGUI.IMGUIManager.Init();
// init anti-anticheat
Logging.MetaDebugLog("Initializing anti-anticheat");
AntiAntiCheatPatch.Init(harmony);
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized");
}
catch (Exception e)
{
HandleError(e, "Failed to initialize the API! Attempting to patch to display error...", OnInitError);
}
}
/// <summary>
/// Shuts down & cleans up the TechbloxModdingAPI.
@ -104,7 +98,7 @@ namespace TechbloxModdingAPI
}
Scheduler.Dispose();
var currentAssembly = Assembly.GetExecutingAssembly();
harmony.UnpatchSelf();
harmony.UnpatchAll(currentAssembly.GetName().Name);
harmony = null;
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} shutdown");
}
@ -115,26 +109,5 @@ namespace TechbloxModdingAPI
ErrorBuilder.DisplayMustQuitError("Failed to patch Techblox!\n" +
"Make sure you're using the latest version of TechbloxModdingAPI or disable mods if the API isn't released yet.");
}
private static void OnInitError()
{
ErrorBuilder.DisplayMustQuitError("Failed to initialize the modding API!\n" +
"Make sure you're using the latest version. If you are, please report the error.");
}
/// <summary>
/// Handles an init error. Logs the exception, a log message, and allows displaying an error in-game.
/// </summary>
/// <param name="e">The exception</param>
/// <param name="logMsg">The log message</param>
/// <param name="onInit">The action to run when the game is ready to display error messages</param>
private static void HandleError(Exception e, string logMsg, Action onInit)
{ //Can't use ErrorBuilder or Logging.LogException (which eventually uses ErrorBuilder) yet
Logging.Log(e.ToString());
Logging.LogWarning(logMsg);
harmony.Patch(AccessTools.Method(typeof(FullGameCompositionRoot), "OnContextInitialized")
.MakeGenericMethod(typeof(UnityContext<FullGameCompositionRoot>)),
new HarmonyMethod(onInit.Method)); //Can't use lambdas here :(
}
}
}

View file

@ -12,7 +12,7 @@ using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Persistence
{
//[HarmonyPatch] - TODO
[HarmonyPatch]
class DeserializeFromDiskEntitiesEnginePatch
{
internal static EntitiesDB entitiesDB = null;
@ -26,7 +26,7 @@ namespace TechbloxModdingAPI.Persistence
SerializerManager.RegisterSerializers(SaveAndLoadCompositionRootPatch.currentEnginesRoot);
uint originalPos = ____serializationData.dataPos;
Logging.MetaDebugLog($"dataPos: {originalPos}");
BinaryBufferReader bbr = new BinaryBufferReader(____bytesStream.ToArrayFast(out int count), ____serializationData.dataPos);
BinaryBufferReader bbr = new BinaryBufferReader(____bytesStream.ToArrayFast(out uint count), ____serializationData.dataPos);
byte[] frameBuffer = new byte[frameStart.Length];
Logging.MetaDebugLog($"serial data count: {____serializationData.data.count} capacity: {____serializationData.data.capacity}");
int i = 0;

View file

@ -4,18 +4,17 @@ using RobocraftX.SaveAndLoad;
using Svelto.ECS;
using HarmonyLib;
using RobocraftX.StateSync;
namespace TechbloxModdingAPI.Persistence
{
[HarmonyPatch(typeof(SaveAndLoadCompositionRoot), "ClientComposeTimeStopped")]
[HarmonyPatch(typeof(SaveAndLoadCompositionRoot), "Compose")]
class SaveAndLoadCompositionRootPatch
{
public static EnginesRoot currentEnginesRoot;
public static void Prefix(StateSyncRegistrationHelper stateSyncHelper)
public static void Prefix(EnginesRoot enginesRoot)
{
currentEnginesRoot = stateSyncHelper.enginesRoot;
currentEnginesRoot = enginesRoot;
//SerializerManager.RegisterSerializers(enginesRoot);
}
}

View file

@ -25,8 +25,8 @@ namespace TechbloxModdingAPI.Persistence
Logging.MetaDebugLog("Skipping component serialization: no serializers registered!");
return;
}
serializationData.data.IncreaseCapacityBy((uint)frameStart.Length);
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out int buffLen), serializationData.dataPos);
serializationData.data.ExpandBy((uint)frameStart.Length);
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out uint buffLen), serializationData.dataPos);
uint originalPos = serializationData.dataPos;
Logging.MetaDebugLog($"dataPos: {originalPos}");
// Add frame start so it's easier to find TechbloxModdingAPI-serialized components
@ -35,14 +35,14 @@ namespace TechbloxModdingAPI.Persistence
bbw.Write(frameStart[i]);
}
Logging.MetaDebugLog($"dataPos (after frame start): {bbw.Position}");
serializationData.data.IncreaseCapacityBy(4u);
serializationData.data.ExpandBy(4u);
bbw.Write((uint)SerializerManager.GetSerializersCount());
string[] serializerKeys = SerializerManager.GetSerializerNames();
for (uint c = 0; c < serializerKeys.Length; c++)
{
Logging.MetaDebugLog($"dataPos (loop start): {bbw.Position}");
// write component info
serializationData.data.IncreaseCapacityBy(4u + (uint)serializerKeys[c].Length);
serializationData.data.ExpandBy(4u + (uint)serializerKeys[c].Length);
bbw.Write((uint)serializerKeys[c].Length);
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}");
byte[] nameBytes = Encoding.UTF8.GetBytes(serializerKeys[c]);
@ -51,7 +51,7 @@ namespace TechbloxModdingAPI.Persistence
bbw.Write(nameBytes[i]);
}
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}");
serializationData.data.IncreaseCapacityBy(4u);
serializationData.data.ExpandBy(4u);
serializationData.dataPos = bbw.Position + 4u;
Logging.MetaDebugLog($"dataPos (now): {bbw.Position}");
Logging.MetaDebugLog($"dataPos (appears to be): {serializationData.dataPos}");
@ -74,7 +74,7 @@ namespace TechbloxModdingAPI.Persistence
public static MethodBase TargetMethod()
{
return AccessTools.TypeByName("RobocraftX.SaveAndLoad.SaveGameEngine").GetMethod("SerializeGameToBuffer");
return typeof(SaveGameEngine).GetMethod("SerializeGameToBuffer");
}
}
}

View file

@ -32,7 +32,7 @@ namespace TechbloxModdingAPI.Persistence
public bool Deserialize(ref ISerializationData serializationData, IEntitySerialization entitySerializer)
{
BinaryBufferReader bbr = new BinaryBufferReader(serializationData.data.ToArrayFast(out int count), serializationData.dataPos);
BinaryBufferReader bbr = new BinaryBufferReader(serializationData.data.ToArrayFast(out uint count), serializationData.dataPos);
uint entityCount = bbr.ReadUint();
serializationData.dataPos = bbr.Position;
for (uint i = 0; i < entityCount; i++)
@ -46,8 +46,8 @@ namespace TechbloxModdingAPI.Persistence
public bool Serialize(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer)
{
serializationData.data.IncreaseCapacityBy(4u);
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out int count), serializationData.dataPos);
serializationData.data.ExpandBy(4u);
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out uint count), serializationData.dataPos);
EGID[] toSerialize = getEntitiesToSerialize(entitiesDB);
bbw.Write((uint)toSerialize.Length);
serializationData.dataPos = bbw.Position;

View file

@ -1,52 +0,0 @@
using System;
using Svelto.ECS;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI
{
public partial class Player
{
internal WrappedHandler<PlayerSeatEventArgs> seatEntered;
public event EventHandler<PlayerSeatEventArgs> SeatEntered
{
add => seatEntered += value;
remove => seatEntered -= value;
}
internal WrappedHandler<PlayerSeatEventArgs> seatExited;
public event EventHandler<PlayerSeatEventArgs> SeatExited
{
add => seatExited += value;
remove => seatExited -= value;
}
internal static WrappedHandler<PlayerEventArgs> joined;
public static event EventHandler<PlayerEventArgs> Joined
{
add => joined += value;
remove => joined -= value;
}
internal static WrappedHandler<PlayerEventArgs> left;
public static event EventHandler<PlayerEventArgs> Left
{
add => left += value;
remove => left -= value;
}
}
public struct PlayerSeatEventArgs
{
public EGID SeatId;
public Seat Seat => (Seat)Block.New(SeatId);
}
public struct PlayerEventArgs
{
public EGID PlayerId;
public Player Player => Player.GetInstance(PlayerId.entityID);
}
}

View file

@ -1,16 +1,13 @@
using System;
using Gamecraft.Wires;
using RobocraftX.Character;
using RobocraftX.Character.Movement;
using Unity.Mathematics;
using RobocraftX.Common;
using RobocraftX.Common.Players;
using RobocraftX.GUI.Wires;
using RobocraftX.Physics;
using Svelto.ECS;
using Techblox.BuildingDrone;
using Techblox.Camera;
using Techblox.Character;
using Techblox.FlyCam;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility;
@ -21,11 +18,10 @@ namespace TechbloxModdingAPI
/// <summary>
/// An in-game player character. Any Leo you see is a player.
/// </summary>
public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID>
public class Player : IEquatable<Player>, IEquatable<EGID>
{
// static functionality
private static readonly PlayerEngine playerEngine = new PlayerEngine();
private static readonly PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine();
private static PlayerEngine playerEngine = new PlayerEngine();
private static Player localPlayer;
/// <summary>
@ -65,32 +61,23 @@ namespace TechbloxModdingAPI
}
/// <summary>
/// Returns the current player belonging to this client. It will be different after entering/leaving simulation.
/// May return null if the local player doesn't exist.
/// Returns the current player belonging to this client.
/// </summary>
public static Player LocalPlayer
{
get
{
var playerId = playerEngine.GetLocalPlayer();
if (playerId == uint.MaxValue) return null;
if (localPlayer == null || localPlayer.Id != playerId)
localPlayer = GetInstance(playerId);
if (localPlayer == null || localPlayer.Id != playerEngine.GetLocalPlayer())
localPlayer = new Player(PlayerType.Local);
return localPlayer;
}
}
internal static Player GetInstance(uint id)
{
return EcsObjectBase.GetInstance(new EGID(id, CharacterExclusiveGroups.OnFootGroup),
e => new Player(e.entityID));
}
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary>
/// <param name="id">The player's unique identifier.</param>
public Player(uint id) : base(new EGID(id, CharacterExclusiveGroups.OnFootGroup))
public Player(uint id)
{
this.Id = id;
if (!Exists(id))
@ -104,32 +91,22 @@ namespace TechbloxModdingAPI
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary>
/// <param name="player">The player type. Chooses the first available player matching the criteria.</param>
public Player(PlayerType player) : base(ecs =>
public Player(PlayerType player)
{
uint id;
switch (player)
{
case PlayerType.Local:
id = playerEngine.GetLocalPlayer();
this.Id = playerEngine.GetLocalPlayer();
break;
case PlayerType.Remote:
id = playerEngine.GetRemotePlayer();
break;
default:
id = uint.MaxValue;
this.Id = playerEngine.GetRemotePlayer();
break;
}
if (id == uint.MaxValue)
if (this.Id == uint.MaxValue)
{
throw new PlayerNotFoundException($"No player of {player} type exists");
}
return new EGID(id, CharacterExclusiveGroups.OnFootGroup);
})
{
this.Type = player;
Id = base.Id.entityID;
}
// object fields & properties
@ -145,7 +122,7 @@ namespace TechbloxModdingAPI
/// The player's unique identifier.
/// </summary>
/// <value>The identifier.</value>
public new uint Id { get; }
public uint Id { get; }
/// <summary>
/// The player's current position.
@ -164,10 +141,10 @@ namespace TechbloxModdingAPI
public float3 Rotation
{
get => ((Quaternion) (GameState.IsBuildMode()
? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation
? playerEngine.GetCameraStruct<CharacterCameraEntityStruct>(Id).Get().rotation
: playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation)).eulerAngles;
set => _ = GameState.IsBuildMode()
? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation = quaternion.Euler(value)
? playerEngine.GetCameraStruct<CharacterCameraEntityStruct>(Id).Get().rotation = quaternion.Euler(value)
: playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation = quaternion.Euler(value);
}
@ -195,8 +172,10 @@ namespace TechbloxModdingAPI
/// The player's mass.
/// </summary>
/// <value>The mass.</value>
[Obsolete] // We cannot get it clientside or something
public float Mass => 0;
public float Mass =>
1f / playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().physicsMass.InverseMass;
private float _ping = -1f;
/// <summary>
/// The player's latest network ping time.
@ -206,7 +185,12 @@ namespace TechbloxModdingAPI
{
get
{
return playerEngine.GetPing() / 1000f;
var opt = playerEngine.GetPlayerStruct<PlayerNetworkStatsEntityStruct>(Id, Type);
if (opt)
{
_ping = opt.Get().lastPingTimeSinceLevelLoad ?? _ping;
}
return _ping;
}
}
@ -214,16 +198,15 @@ namespace TechbloxModdingAPI
/// The player's initial health when entering Simulation (aka Time Running) mode.
/// </summary>
/// <value>The initial health.</value>
[Obsolete("We can no longer get initial health, returns max health.")]
public float InitialHealth
{
get
{
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id);
return opt ? opt.Get().maxHealth : -1f;
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
return opt ? opt.Get().initialHealth : -1f;
}
set => playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id).Get().maxHealth = value;
set => playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get().initialHealth = value;
}
/// <summary>
@ -234,25 +217,30 @@ namespace TechbloxModdingAPI
{
get
{
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id);
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
return opt ? opt.Get().currentHealth : -1f;
}
set => playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id).Get().currentHealth = value;
set => playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get().currentHealth = value;
}
/// <summary>
/// Whether this <see cref="T:TechbloxModdingAPI.Player"/> is damageable.
/// </summary>
/// <value><c>true</c> if damageable; otherwise, <c>false</c>.</value>
[Obsolete("Players are probably always damageable")]
public bool Damageable
{
get => true;
get
{
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
return opt.Get().canTakeDamageStat;
}
// ReSharper disable once ValueParameterNotUsed
set
{
ref var healthStruct = ref playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get();
healthStruct.canTakeDamage = value;
healthStruct.canTakeDamageStat = value;
}
}
@ -260,26 +248,30 @@ namespace TechbloxModdingAPI
/// The player's lives when initially entering Simulation (aka Time Running) mode.
/// </summary>
/// <value>The initial lives.</value>
[Obsolete("The player has infinite lives")]
public uint InitialLives
{
get => uint.MaxValue;
get
{
var opt = playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id);
return opt ? opt.Get().initialLives : uint.MaxValue;
}
// ReSharper disable once ValueParameterNotUsed
set { }
set => playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id).Get().initialLives = value;
}
/// <summary>
/// The player's current lives in Simulation (aka Time Running) mode.
/// </summary>
/// <value>The current lives.</value>
[Obsolete("The player has infinite lives")]
public uint CurrentLives
{
get => uint.MaxValue;
get
{
var opt = playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id);
return opt ? opt.Get().currentLives : uint.MaxValue;
}
// ReSharper disable once ValueParameterNotUsed
set { }
set => playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id).Get().currentLives = value;
}
/*/// <summary>
@ -310,7 +302,7 @@ namespace TechbloxModdingAPI
get
{
var optstruct = playerEngine.GetCharacterStruct<EquippedPartStruct>(Id);
return optstruct ? (BlockIDs) optstruct.Get().selectedDBPartID : BlockIDs.Invalid;
return optstruct ? (BlockIDs) optstruct.Get().SelectedDBPartID : BlockIDs.Invalid;
}
}
@ -356,20 +348,8 @@ namespace TechbloxModdingAPI
/// <summary>
/// The player's mode in time stopped mode, determining what they place.
/// </summary>
public PlayerBuildingMode BuildingMode => (PlayerBuildingMode)Math.Log((double)playerEngine
.GetCharacterStruct<TimeStoppedModeComponent>(Id).Get().timeStoppedContext, 2); // It's a bit field in game now
public PlayerState State =>
playerEngine.GetCharacterStruct<CharacterTagEntityStruct>(Id).Get().ID.groupID switch
{
var group when group == CharacterExclusiveGroups.MachineSpawningGroup => PlayerState.HoldingMachine,
var group when group == CharacterExclusiveGroups.OnFootGroup => PlayerState.OnFoot,
var group when group == CharacterExclusiveGroups.InPilotSeatGroup => PlayerState.InSeat,
var group when group == CharacterExclusiveGroups.DyingOnFootGroup => PlayerState.OnFoot,
var group when group == CharacterExclusiveGroups.DyingInPilotSeatGroup => PlayerState.InSeat,
var group when group == CharacterExclusiveGroups.DeadGroup => PlayerState.OnFoot,
_ => throw new ArgumentOutOfRangeException("", "Unknown player state")
};
public PlayerBuildingMode BuildingMode => (PlayerBuildingMode) playerEngine
.GetCharacterStruct<PlayerInputTimeStoppedContextStruct>(Id).Get().timeStoppedContext;
/// <summary>
/// Whether the player is sprinting.
@ -377,10 +357,10 @@ namespace TechbloxModdingAPI
public bool Sprinting
{
get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting
? playerEngine.GetCharacterStruct<FlyCamMovementComponent>(Id).Get().sprinting
: playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting;
set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting = value
? playerEngine.GetCharacterStruct<FlyCamMovementComponent>(Id).Get().sprinting = value
: playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting = value;
}
@ -390,10 +370,10 @@ namespace TechbloxModdingAPI
public float SpeedSetting
{
get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed
? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().speed
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed;
set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed = value
? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().speed = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed = value;
}
@ -403,10 +383,10 @@ namespace TechbloxModdingAPI
public float SpeedSprintMultiplierSetting
{
get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier
? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().speedSprintMultiplier
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier;
set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier = value
? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().speedSprintMultiplier = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier = value;
}
@ -416,10 +396,10 @@ namespace TechbloxModdingAPI
public float AccelerationSetting
{
get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration
? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().acceleration
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration;
set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration = value
? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().acceleration = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration = value;
}
@ -443,39 +423,6 @@ namespace TechbloxModdingAPI
playerEngine.SetLocation(Id, location, exitSeat: exitSeat);
}
/// <summary>
/// Enter the given seat.
/// </summary>
/// <param name="seat">The seat to enter.</param>
public void EnterSeat(Seat seat)
{
playerEngine.EnterSeat(Id, seat.Id);
}
/// <summary>
/// Exit the seat the player is currently in.
/// </summary>
public void ExitSeat()
{
playerEngine.ExitSeat(Id);
}
/// <summary>
/// Spawn the machine the player is holding in time running mode.
/// </summary>
public bool SpawnMachine()
{
return playerEngine.SpawnMachine(Id);
}
/// <summary>
/// Despawn the player's machine in time running mode and place it in their hand.
/// </summary>
public bool DespawnMachine()
{
return playerEngine.DespawnMachine(Id);
}
/// <summary>
/// Returns the block the player is currently looking at in build mode.
/// </summary>
@ -484,8 +431,7 @@ namespace TechbloxModdingAPI
public Block GetBlockLookedAt(float maxDistance = -1f)
{
var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
return egid != default && egid.groupID != CommonExclusiveGroups.SIMULATION_BODIES_GROUP
&& egid.groupID != WiresGUIExclusiveGroups.WireGroup
return egid != EGID.Empty && egid.groupID != CommonExclusiveGroups.SIMULATION_BODIES_GROUP
? Block.New(egid)
: null;
}
@ -498,22 +444,8 @@ namespace TechbloxModdingAPI
public SimBody GetSimBodyLookedAt(float maxDistance = -1f)
{
var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
return egid != default && egid.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP
? EcsObjectBase.GetInstance(egid, e => new SimBody(e))
: null;
}
/// <summary>
/// Returns the wire the player is currently looking at in build mode.
/// </summary>
/// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param>
/// <returns>The wire or null if not found</returns>
public Wire GetWireLookedAt(float maxDistance = -1f)
{
var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
return egid != default && egid.groupID == WiresGUIExclusiveGroups.WireGroup
? EcsObjectBase.GetInstance(new EGID(egid.entityID, BuildModeWiresGroups.WiresGroup.Group),
e => new Wire(e))
return egid != EGID.Empty && egid.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP
? new SimBody(egid)
: null;
}
@ -526,15 +458,6 @@ namespace TechbloxModdingAPI
return playerEngine.GetSelectedBlocks(Id);
}
/// <summary>
/// Returns the ghost block that shows the block to be placed in build mode.
/// </summary>
/// <returns>A block instance or null if not found</returns>
public Block GetGhostBlock()
{
return playerEngine.GetGhostBlock(Id);
}
public bool Equals(Player other)
{
if (ReferenceEquals(null, other)) return false;
@ -562,17 +485,11 @@ namespace TechbloxModdingAPI
return (int) Id;
}
public override string ToString()
{
return $"{nameof(Type)}: {Type}, {nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}, {nameof(Mass)}: {Mass}";
}
// internal methods
internal static void Init()
{
Utility.GameEngineManager.AddGameEngine(playerEngine);
Utility.GameEngineManager.AddGameEngine(playerEventsEngine);
}
}
}

View file

@ -6,7 +6,6 @@
ColourMode,
ConfigMode,
BlueprintMode,
MaterialMode,
LandscapeMode
MaterialMode
}
}

View file

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using RobocraftX.Character;
@ -8,24 +10,22 @@ using RobocraftX.Common.Input;
using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.Physics;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Common;
using RobocraftX.Multiplayer;
using RobocraftX.SimulationModeState;
using Gamecraft.GUI.HUDFeedbackBlocks;
using Svelto.ECS;
using Techblox.Camera;
using Techblox.FlyCam;
using Unity.Mathematics;
using Unity.Physics;
using UnityEngine;
using HarmonyLib;
using RobocraftX.Common;
using Svelto.ECS.DataStructures;
using Techblox.BuildingDrone;
using Techblox.Character;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Players
{
internal class PlayerEngine : IFunEngine
internal class PlayerEngine : IApiEngine, IFactoryEngine
{
public string Name { get; } = "TechbloxModdingAPIPlayerGameEngine";
@ -33,7 +33,7 @@ namespace TechbloxModdingAPI.Players
public bool isRemovable => false;
public IEntityFunctions Functions { get; set; }
public IEntityFactory Factory { set; private get; }
private bool isReady = false;
@ -50,10 +50,10 @@ namespace TechbloxModdingAPI.Players
public uint GetLocalPlayer()
{
if (!isReady) return uint.MaxValue;
var (localPlayers, count) = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers);
if (count > 0)
var localPlayers = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers).ToBuffer();
if (localPlayers.count > 0)
{
return localPlayers[0].ID.entityID;
return localPlayers.buffer[0].ID.entityID;
}
return uint.MaxValue;
}
@ -61,10 +61,10 @@ namespace TechbloxModdingAPI.Players
public uint GetRemotePlayer()
{
if (!isReady) return uint.MaxValue;
var (localPlayers, count) = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers);
if (count > 0)
var localPlayers = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers).ToBuffer();
if (localPlayers.count > 0)
{
return localPlayers[0].ID.entityID;
return localPlayers.buffer[0].ID.entityID;
}
return uint.MaxValue;
}
@ -107,16 +107,26 @@ namespace TechbloxModdingAPI.Players
return false;
if (group == CharacterExclusiveGroups.InPilotSeatGroup && exitSeat)
{
ExitSeat(playerId);
EGID egid = new EGID(playerId, group);
entitiesDB.QueryEntity<CharacterPilotSeatEntityStruct>(egid).instantExit = true;
entitiesDB.PublishEntityChange<CharacterPilotSeatEntityStruct>(egid);
}
rbesOpt.Get().position = location;
return true;
}
public bool GetGameOverScreen(uint playerId)
{
if (entitiesDB == null) return false;
ref HudActivatedBlocksEntityStruct habes = ref entitiesDB.QueryEntity<HudActivatedBlocksEntityStruct>(HUDFeedbackBlocksGUIExclusiveGroups.GameOverHudEgid);
NativeDynamicArrayCast<EGID> nativeDynamicArrayCast = new NativeDynamicArrayCast<EGID>(habes.activatedBlocksOrdered);
return nativeDynamicArrayCast.count > 0;
}
public bool IsDead(uint playerId)
{
if (entitiesDB == null) return true;
return entitiesDB.Exists<RigidBodyEntityStruct>(playerId, CharacterExclusiveGroups.DeadGroup);
return entitiesDB.Exists<RigidBodyEntityStruct>(playerId, CharacterExclusiveGroups.DeadCharacters);
}
// reusable methods
@ -130,7 +140,7 @@ namespace TechbloxModdingAPI.Players
{
group = default;
if (GameState.IsBuildMode())
return entitiesDB.QueryEntityOptional<T>(new EGID(playerId, LocalBuildingDrone.BuildGroup));
return entitiesDB.QueryEntityOptional<T>(new EGID(playerId, Techblox.FlyCam.FlyCam.Group));
var characterGroups = CharacterExclusiveGroups.AllCharacters;
for (int i = 0; i < characterGroups.count; i++)
@ -157,35 +167,34 @@ namespace TechbloxModdingAPI.Players
public OptionalRef<T> GetCameraStruct<T>(uint playerId) where T : unmanaged, IEntityComponent
{
return entitiesDB.QueryEntityOptional<T>(new EGID(playerId, CameraExclusiveGroups.PhysicCameraGroup));
return entitiesDB.QueryEntityOptional<T>(new EGID(playerId, CameraExclusiveGroups.CameraGroup));
}
public EGID GetThingLookedAt(uint playerId, float maxDistance = -1f)
{
var opt = GetCameraStruct<PhysicCameraRayCastEntityStruct>(playerId);
if (!opt) return default;
PhysicCameraRayCastEntityStruct rayCast = opt;
EGID physicCameraEgid = new EGID(playerId, CameraExclusiveGroups.PhysicCameraGroup);
var opt = GetCameraStruct<CharacterCameraRayCastEntityStruct>(playerId);
if (!opt) return EGID.Empty;
CharacterCameraRayCastEntityStruct rayCast = opt;
float distance = maxDistance < 0
? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast, physicCameraEgid,
? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast,
GhostBlockUtils.GhostCastMethod.GhostCastProportionalToBlockSize)
: maxDistance;
if (rayCast.hit && rayCast.distance <= distance)
return rayCast.hitEgid; //May be EGID.Empty (default)
return rayCast.hitEgid; //May be EGID.Empty
return default;
return EGID.Empty;
}
public unsafe Block[] GetSelectedBlocks(uint playerid)
{
if (!entitiesDB.Exists<BoxSelectStateEntityStruct>(playerid,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup))
return Array.Empty<Block>();
return new Block[0];
var state = entitiesDB.QueryEntity<BoxSelectStateEntityStruct>(playerid,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup);
var blocks = entitiesDB.QueryEntity<SelectedBlocksStruct>(playerid,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup);
if (!state.active) return Array.Empty<Block>();
if (!state.active) return new Block[0];
var pointer = (EGID*) blocks.selectedBlocks.ToPointer();
var ret = new Block[blocks.count];
for (int j = 0; j < blocks.count; j++)
@ -196,79 +205,5 @@ namespace TechbloxModdingAPI.Players
return ret;
}
public void EnterSeat(uint playerId, EGID seatId)
{
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB))
return;
/*PilotSeatGroupUtils.SwapTagTo<OCCUPIED_TAG>(Functions, seatId);
var opt = GetCharacterStruct<CharacterPilotSeatEntityStruct>(playerId, out var group);
if (!opt) return; - TODO: This is server code and mods run in client code atm. We can only send inputs even in singleplayer as it is.
ref CharacterPilotSeatEntityStruct charSeat = ref opt.Get();
var charId = new EGID(playerId, group);
charSeat.pilotSeatEntity = entitiesDB.GetEntityReference(seatId);
charSeat.entryPositionOffset =
entitiesDB.QueryEntity<PositionEntityStruct>(charId).position -
entitiesDB.QueryEntity<PositionEntityStruct>(seatId).position;
ref var seat = ref entitiesDB.QueryEntity<PilotSeatEntityStruct>(seatId);
seat.occupyingCharacter = entitiesDB.GetEntityReference(charId);
charSeat.followCam = entitiesDB.QueryEntity<SeatFollowCamComponent>(seatId).followCam;
Functions.SwapEntityGroup<CharacterEntityDescriptor>(charId, CharacterExclusiveGroups.InPilotSeatGroup);*/
}
public void ExitSeat(uint playerId)
{
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB))
return;
/*EGID egid = new EGID(playerId, CharacterExclusiveGroups.InPilotSeatGroup);
var opt = entitiesDB.QueryEntityOptional<CharacterPilotSeatEntityStruct>(egid);
if (!opt) return;
opt.Get().instantExit = true;
entitiesDB.PublishEntityChange<CharacterPilotSeatEntityStruct>(egid);*/
}
public bool SpawnMachine(uint playerId)
{
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB))
return false;
EGID egid = new EGID(playerId, CharacterExclusiveGroups.MachineSpawningGroup);
if (!entitiesDB.Exists<CharacterTagEntityStruct>(egid))
return false;
if (entitiesDB.QueryEntity<CharacterMachineSpawningValidityComponent>(egid).isMachinePlacementInvalid)
{
Logging.MetaDebugLog("Machine placement invalid");
return false;
}
//Functions.SwapEntityGroup<CharacterEntityDescriptor>(egid, CharacterExclusiveGroups.OnFootGroup);
FakeInput.ActionInput(playerId, primary: true);
return true;
}
public bool DespawnMachine(uint playerId)
{
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB))
return false;
GetCharacterStruct<CharacterTagEntityStruct>(playerId, out var group);
if (group.isInvalid)
return false;
EGID egid = new EGID(playerId, group);
if (!entitiesDB.Exists<CharacterTagEntityStruct>(egid))
return false;
Functions.SwapEntityGroup<CharacterEntityDescriptor>(egid, CharacterExclusiveGroups.MachineSpawningGroup);
return true;
}
public uint GetPing()
{
return entitiesDB
.QueryUniqueEntity<NetworkStatsEntityStruct>(MultiplayerExclusiveGroups.MultiplayerStateGroup)
.networkStats.PingMs;
}
public Block GetGhostBlock(uint playerId)
{
var egid = new EGID(playerId, GHOST_BLOCKS_ENABLED.Group);
return entitiesDB.Exists<DBEntityStruct>(egid) ? Block.New(egid) : null;
}
}
}

View file

@ -1,45 +0,0 @@
using System;
using RobocraftX.Character;
using RobocraftX.Character.Movement;
using RobocraftX.Common.Input;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Players
{
public class PlayerEventsEngine : IApiEngine, IReactOnSwap<CharacterPilotSeatEntityStruct>, IReactOnAddAndRemove<PlayerIDStruct>
{
public void Ready()
{
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
}
public string Name => "TechbloxModdingAPIPlayerEventsEngine";
public bool isRemovable => false;
public void MovedTo(ref CharacterPilotSeatEntityStruct entityComponent, ExclusiveGroupStruct previousGroup, EGID egid)
{
entitiesDB.TryGetEGID(entityComponent.pilotSeatEntity, out var seatId); //TODO: Can't get EGID
var player = Player.GetInstance(egid.entityID);
if (previousGroup == CharacterExclusiveGroups.InPilotSeatGroup)
player.seatExited.Invoke(this, new PlayerSeatEventArgs { SeatId = seatId});
else if (egid.groupID == CharacterExclusiveGroups.InPilotSeatGroup)
player.seatEntered.Invoke(this, new PlayerSeatEventArgs { SeatId = seatId });
}
public void Add(ref PlayerIDStruct entityComponent, EGID egid)
{
Player.joined.Invoke(this, new PlayerEventArgs { PlayerId = egid });
}
public void Remove(ref PlayerIDStruct entityComponent, EGID egid)
{
Player.left.Invoke(this, new PlayerEventArgs { PlayerId = egid });
}
}
}

View file

@ -1,9 +0,0 @@
namespace TechbloxModdingAPI.Players
{
public enum PlayerState
{
HoldingMachine,
OnFoot,
InSeat
}
}

View file

@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
using Svelto.Tasks;
using Svelto.Tasks.Enumerators;
using Unity.Mathematics;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI;
using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Players
{
@ -29,63 +24,13 @@ namespace TechbloxModdingAPI.Players
[APITestCase(TestType.EditMode)]
public static void PositionTest()
{
Player p = Player.LocalPlayer;
Player p = new Player(PlayerType.Local);
if (!Assert.Errorless(() => { p.Teleport(0, 0, 0, relative: false); }, "Player.Teleport(origin) errored: ", "Player teleported to origin successfully.")) return;
if (!Assert.CloseTo(p.Position, float3.zero, "Player is not close to origin despite being teleported there.", "Player.Position is at origin.")) return;
if (!Assert.Errorless(() => { p.Position = float3.zero + 1; }, "Player.Position = origin+1 errored: ", "Player moved to origin+1.")) return;
Assert.CloseTo(p.Position, float3.zero + 1, "Player is not close to origin+1 despite being teleported there.", "Player.Position is at origin+1.");
}
[APITestCase(TestType.Game)]
public static void SeatEventTestBuild()
{
Block.PlaceNew(BlockIDs.DriverSeat, Player.LocalPlayer.Position);
}
[APITestCase(TestType.SimulationMode)]
public static IEnumerator<TaskContract> SeatEventTestSim()
{
Player.LocalPlayer.SeatEntered += Assert.CallsBack<PlayerSeatEventArgs>("SeatEntered");
Player.LocalPlayer.SeatExited += Assert.CallsBack<PlayerSeatEventArgs>("SeatExited");
Assert.Equal(Player.LocalPlayer.SpawnMachine(), true, "Failed to spawn the player's machine.", "Successfully spawned the player's machine.");
yield return new WaitForSecondsEnumerator(1).Continue();
var seats = Game.CurrentGame().GetBlocksInGame(BlockIDs.DriverSeat);
int c = 0;
while (seats.Length == 0 && c < 10)
{
Logging.MetaLog("Waiting for a seat to be spawned...");
yield return new WaitForSecondsEnumerator(1).Continue();
Logging.MetaLog("Spawn machine: " + Player.LocalPlayer.SpawnMachine());
seats = Game.CurrentGame().GetBlocksInGame(BlockIDs.DriverSeat);
c++;
}
if (seats.Length == 0)
{
Assert.Fail("No driver seat found!");
yield break;
}
if (seats[0] is Seat seat)
{
Assert.Errorless(() => Player.LocalPlayer.EnterSeat(seat), "Failed to enter seat.",
"Entered seat successfully.");
while (Player.LocalPlayer.State != PlayerState.InSeat)
{
bool cont = false;
Client.Instance.PromptUser(new SingleChoicePrompt("Testing", $"Enter the seat at {seat.Position} pls", "OK", () => cont = true));
while (!cont)
yield return Yield.It;
yield return new WaitForSecondsEnumerator(5f).Continue();
}
}
else
Assert.Fail("Found a seat that is not a seat!");
yield return new WaitForSecondsEnumerator(5).Continue();
Assert.Errorless(() => Player.LocalPlayer.ExitSeat(), "Failed to exit seat.",
"Exited seat successfully.");
}
[APITestCase(TestType.Menu)]
public static void InvalidStateTest()
{

View file

@ -6,7 +6,6 @@ using UnityEngine;
using Gamecraft.Damage;
using RobocraftX.Common;
using RobocraftX.Physics;
using Techblox.TimeRunning.Clusters;
namespace TechbloxModdingAPI
{
@ -15,20 +14,20 @@ namespace TechbloxModdingAPI
/// </summary>
public class SimBody : EcsObjectBase, IEquatable<SimBody>, IEquatable<EGID>
{
public override EGID Id { get; }
/// <summary>
/// The cluster this chunk belongs to, or null if no cluster destruction manager present or the chunk doesn't exist.
/// Get the SimBody from a Block if possible for good performance here.
/// </summary>
public Cluster Cluster => cluster ??= clusterId == uint.MaxValue // Return cluster or if it's null then set it
? Block.BlockEngine.GetCluster(Id.entityID) // If we don't have a clusterId set then get it from the game
: GetInstance(new EGID(clusterId, ClustersExclusiveGroups.SIMULATION_CLUSTERS_GROUP),
egid => new Cluster(egid)); // Otherwise get the cluster from the ID
public Cluster Cluster => cluster ?? (cluster = clusterId == uint.MaxValue ? Block.BlockEngine.GetCluster(Id.entityID) : new Cluster(clusterId));
private Cluster cluster;
private readonly uint clusterId = uint.MaxValue;
public SimBody(EGID id) : base(id)
public SimBody(EGID id)
{
Id = id;
}
public SimBody(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_BODIES_GROUP))
@ -74,16 +73,15 @@ namespace TechbloxModdingAPI
}
}
[Obsolete] //Cannot get mass even from UECS
public float Mass
{
get => 0f;
get => math.rcp(GetStruct().physicsMass.InverseMass);
//set => GetStruct().physicsMass.InverseMass = math.rcp(value);
}
public float3 CenterOfMass
{
get => 0f; //TODO
get => GetStruct().physicsMass.CenterOfMass;
//set => GetStruct().physicsMass.CenterOfMass = value;
}
@ -94,20 +92,20 @@ namespace TechbloxModdingAPI
public float InitialHealth
{
get => 0f;
set { }
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth;
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth = value;
}
public float CurrentHealth
{
get => 0f;
set { }
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth;
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth = value;
}
public float HealthMultiplier
{
get => 0f;
set { }
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier;
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier = value;
}
/// <summary>

View file

@ -1,17 +0,0 @@
using System.Collections;
using RobocraftX.Schedulers;
using Svelto.Tasks;
using Svelto.Tasks.ExtraLean;
using Svelto.Tasks.Unity.Internal;
namespace TechbloxModdingAPI.Tasks
{
public class OnGuiRunner : SteppableRunner<ExtraLeanSveltoTask<IEnumerator>>
{
public OnGuiRunner(string name, uint runningOrder = 0)
: base(name)
{
UnityCoroutineRunner.StartOnGuiCoroutine(this._processEnumerator, runningOrder);
}
}
}

View file

@ -20,7 +20,7 @@ namespace TechbloxModdingAPI.Tasks
{
get
{
return RobocraftX.Schedulers.ClientLean.UIScheduler;
return RobocraftX.Schedulers.Lean.UIScheduler;
}
}
@ -28,7 +28,7 @@ namespace TechbloxModdingAPI.Tasks
{
get
{
return RobocraftX.Schedulers.ClientExtraLean.UIScheduler;
return RobocraftX.Schedulers.ExtraLean.UIScheduler;
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,33 +1,64 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using HarmonyLib;
using RobocraftX.FrontEnd;
using TechbloxModdingAPI.App;
using HarmonyLib;
using IllusionInjector;
// test
using RobocraftX.FrontEnd;
using Unity.Mathematics;
using UnityEngine;
using RobocraftX.Common.Input;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Interface.IMGUI;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Tests
{
#if DEBUG // The API should be loaded by other plugins, but it can be used by itself for testing
[BepInPlugin("org.exmods.TechbloxModdingAPIPluginTest", PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
[BepInProcess("Techblox.exe")]
public class TechbloxModdingAPIPluginTest : BaseUnityPlugin
#if DEBUG
// unused by design
/// <summary>
/// Modding API implemented as a standalone IPA Plugin.
/// Ideally, TechbloxModdingAPI should be loaded by another mod; not itself
/// </summary>
class TechbloxModdingAPIPluginTest : IllusionPlugin.IEnhancedPlugin
{
private void Awake()
{
Main.Init();
Client.EnterMenu += (sender, args) => throw new Exception("Test handler always throws an exception!");
Client.EnterMenu += (sender, args) => Console.WriteLine("EnterMenu handler after erroring handler");
Game.Enter += (s, a) =>
{
Player.LocalPlayer.SeatEntered += (sender, args) =>
Console.WriteLine($"Player {Player.LocalPlayer} entered seat {args.Seat}");
Player.LocalPlayer.SeatExited += (sender, args) =>
Console.WriteLine($"Player {Player.LocalPlayer} exited seat {args.Seat}");
};
private static Harmony harmony { get; set; }
public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
public string HarmonyID { get; } = "org.git.exmods.modtainers.techbloxmoddingapi";
public override void OnApplicationQuit()
{
Main.Shutdown();
}
public override void OnApplicationStart()
{
FileLog.Reset();
Harmony.DEBUG = true;
Main.Init();
Logging.MetaDebugLog($"Version group id {(uint)ApiExclusiveGroups.versionGroup}");
// disable background music
Logging.MetaDebugLog("Audio Mixers: " + string.Join(",", AudioTools.GetMixers()));
//AudioTools.SetVolume(0.0f, "Music"); // The game now sets this from settings again after this is called :(
//Utility.VersionTracking.Enable();//(very) unstable
// debug/test handlers
Client.EnterMenu += (sender, args) => throw new Exception("Test handler always throws an exception!");
// debug/test commands
if (Dependency.Hell("ExtraCommands"))
{
CommandBuilder.Builder()
.Name("Exit")
.Description("Close Techblox immediately, without any prompts")
@ -40,44 +71,218 @@ namespace TechbloxModdingAPI.Tests
.Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
.Build();
CommandBuilder.Builder()
.Name("MoveLastBlock")
.Description("Move the most-recently-placed block, and any connected blocks by the given offset")
.Action((float x, float y, float z) =>
{
if (GameState.IsBuildMode())
foreach (var block in Block.GetLastPlacedBlock().GetConnectedCubes())
block.Position += new Unity.Mathematics.float3(x, y, z);
else
Logging.CommandLogError("Blocks can only be moved in Build mode!");
}).Build();
CommandBuilder.Builder()
.Name("PlaceAluminium")
.Description("Place a block of aluminium at the given coordinates")
.Action((float x, float y, float z) =>
{
var block = Block.PlaceNew(BlockIDs.Cube, new float3(x, y, z));
Logging.CommandLog("Block placed with type: " + block.Type);
})
.Build();
CommandBuilder.Builder()
.Name("PlaceAluminiumLots")
.Description("Place a lot of blocks of aluminium at the given coordinates")
.Action((float x, float y, float z) =>
{
Logging.CommandLog("Starting...");
var sw = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
for (int j = 0; j < 100; j++)
Block.PlaceNew(BlockIDs.Cube, new float3(x + i, y, z + j));
sw.Stop();
Logging.CommandLog("Finished in " + sw.ElapsedMilliseconds + "ms");
})
.Build();
Block b = null;
CommandBuilder.Builder("moveBlockInSim", "Run in build mode first while looking at a block, then in sim to move it up")
.Action(() =>
{
if (b == null)
{
b = new Player(PlayerType.Local).GetBlockLookedAt();
Logging.CommandLog("Block saved: " + b);
}
else
Logging.CommandLog("Block moved to: " + (b.GetSimBody().Position += new float3(0, 2, 0)));
}).Build();
CommandBuilder.Builder("Error", "Throw an error to make sure SimpleCustomCommandEngine's wrapper catches it.")
.Action(() => { throw new Exception("Error Command always throws an error"); })
.Build();
CommandBuilder.Builder("ColorBlock",
"Change color of the block looked at if there's any.")
.Action<string>(str =>
{
if (!Enum.TryParse(str, out BlockColors color))
{
Logging.CommandLog("Color " + str + " not found! Interpreting as 4 color values.");
var s = str.Split(' ');
new Player(PlayerType.Local).GetBlockLookedAt().CustomColor = new float4(float.Parse(s[0]),
float.Parse(s[1]), float.Parse(s[2]), float.Parse(s[3]));
return;
}
new Player(PlayerType.Local).GetBlockLookedAt().Color = color;
Logging.CommandLog("Colored block to " + color);
}).Build();
CommandBuilder.Builder("MoveBlockByID", "Gets a block based on its object identifier and teleports it up.")
.Action<char>(ch =>
{
foreach (var body in SimBody.GetFromObjectID(ch))
{
Logging.CommandLog("SimBody: " + body);
body.Position += new float3(0, 10, 0);
foreach (var bodyConnectedBody in body.GetConnectedBodies())
{
Logging.CommandLog("Moving " + bodyConnectedBody);
bodyConnectedBody.Position += new float3(0, 10, 0);
}
}
}).Build();
CommandBuilder.Builder("TestChunkHealth", "Sets the chunk looked at to the given health.")
.Action((float val, float max) =>
{
var body = new Player(PlayerType.Local).GetSimBodyLookedAt();
if (body == null) return;
body.CurrentHealth = val;
body.InitialHealth = max;
Logging.CommandLog("Health set to: " + val);
}).Build();
CommandBuilder.Builder("placeBlockGroup", "Places some blocks in a group")
.Action((float x, float y, float z) =>
{
var pos = new float3(x, y, z);
var group = BlockGroup.Create(new Block(BlockIDs.Cube, pos) {Color = BlockColors.Aqua});
new Block(BlockIDs.Cube, pos += new float3(1, 0, 0))
{Color = BlockColors.Blue, BlockGroup = group};
new Block(BlockIDs.Cube, pos += new float3(1, 0, 0))
{Color = BlockColors.Green, BlockGroup = group};
new Block(BlockIDs.Cube, pos + new float3(1, 0, 0))
{Color = BlockColors.Lime, 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();
CommandBuilder.Builder("toggleTimeMode", "Enters or exits simulation.")
.Action((float x, float y, float z) =>
{
Game.CurrentGame().ToggleTimeMode();
}).Build();
CommandBuilder.Builder("testColorBlock", "Tests coloring a block to default color")
.Action(() => Player.LocalPlayer.GetBlockLookedAt().Color = BlockColors.Default).Build();
CommandBuilder.Builder("testMaterialBlock", "Tests materialing a block to default material")
.Action(() => Player.LocalPlayer.GetBlockLookedAt().Material = BlockMaterial.Default).Build();
CommandBuilder.Builder("testGameName", "Tests changing the game name")
.Action(() => Game.CurrentGame().Name = "Test").Build();
CommandBuilder.Builder("makeBlockStatic", "Makes a block you look at static")
.Action(() => Player.LocalPlayer.GetBlockLookedAt().Static = true).Build();
Game.AddPersistentDebugInfo("InstalledMods", InstalledMods);
Block.Placed += (sender, args) =>
Logging.MetaDebugLog("Placed block " + args.Block);
Block.Removed += (sender, args) =>
Logging.MetaDebugLog("Removed block " + args.Block);
}
// Plugin startup logic
Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!");
// dependency test
if (Dependency.Hell("TechbloxScripting", new Version("0.0.1.0")))
{
Logging.LogWarning("You're in TechbloxScripting dependency hell");
}
else
{
Logging.Log("Compatible TechbloxScripting detected");
}
// Interface test
/*Group uiGroup = new Group(new Rect(20, 20, 200, 500), "TechbloxModdingAPI_UITestGroup", true);
var button = new Button("TEST");
button.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
var button2 = new Button("TEST2");
button2.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
Text uiText = new Text("<Input!>", multiline: true);
uiText.OnEdit += (t, txt) => { Logging.MetaDebugLog($"Text in {((Text)t).Name} is now '{txt}'"); };
Label uiLabel = new Label("Label!");
Image uiImg = new Image(name:"Behold this texture!");
uiImg.Enabled = false;
uiGroup.AddElement(button);
uiGroup.AddElement(button2);
uiGroup.AddElement(uiText);
uiGroup.AddElement(uiLabel);
uiGroup.AddElement(uiImg);*/
/*Addressables.LoadAssetAsync<Texture2D>("Assets/Art/Textures/UI/FrontEndMap/RCX_Blue_Background_5k.jpg")
.Completed +=
handle =>
{
uiImg.Texture = handle.Result;
uiImg.Enabled = true;
Logging.MetaDebugLog($"Got blue bg asset {handle.Result}");
};*/
/*((FasterList<GuiInputMap.GuiInputMapElement>)AccessTools.Property(typeof(GuiInputMap), "GuiInputsButtonDown").GetValue(null))
.Add(new GuiInputMap.GuiInputMapElement(RewiredConsts.Action.ToggleCommandLine, GuiIn))*/
#if TEST
TestRoot.RunTests();
#endif
}
private void OnDestroy()
{
Main.Shutdown();
}
private string modsString;
private string InstalledMods()
{
if (modsString != null) return modsString;
StringBuilder sb = new StringBuilder("Installed mods:");
foreach (var (_, plugin) in Chainloader.PluginInfos)
sb.Append("\n" + plugin.Metadata.Name + " - " + plugin.Metadata.Version);
foreach (var plugin in PluginManager.Plugins)
sb.Append("\n" + plugin.Name + " - " + plugin.Version);
return modsString = sb.ToString();
}
public override void OnUpdate()
{
if (UnityEngine.Input.GetKeyDown(KeyCode.End))
{
Console.WriteLine("Pressed button to toggle console");
FakeInput.CustomInput(new LocalInputEntityStruct {commandLineToggleInput = true});
}
}
[HarmonyPatch]
public class MinimumSpecsPatch
{
public static bool Prefix(ref bool __result)
public static bool Prefix()
{
__result = true;
return false;
}
public static MethodInfo TargetMethod()
{
return ((Func<bool>) MinimumSpecsCheck.CheckRequirementsMet).Method;
return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method;
}
}
}
#endif

View file

@ -7,9 +7,7 @@ using System.Linq; // welcome to the dark side
using Svelto.Tasks;
using Svelto.Tasks.Lean;
using Svelto.Tasks.Enumerators;
using Svelto.Tasks.Lean.Unity;
using UnityEngine;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility;
@ -66,7 +64,7 @@ namespace TechbloxModdingAPI.Tests
_testsCountPassed = 0;
_testsCountFailed = 0;
// flow control
Game.Enter += (sender, args) => { GameTests().RunOn(new UpdateMonoRunner("TechbloxModdingAPITestRunner")); };
Game.Enter += (sender, args) => { GameTests().RunOn(RobocraftX.Schedulers.Lean.EveryFrameStepRunner_TimeRunningAndStopped); };
Game.Exit += (s, a) => state = "ReturningFromGame";
Client.EnterMenu += (sender, args) =>
{
@ -131,7 +129,7 @@ namespace TechbloxModdingAPI.Tests
private static IEnumerator<TaskContract> GoToGameTests()
{
Client app = Client.Instance;
Client app = new Client();
int oldLength = 0;
while (app.MyGames.Length == 0 || oldLength != app.MyGames.Length)
{
@ -139,15 +137,7 @@ namespace TechbloxModdingAPI.Tests
yield return new WaitForSecondsEnumerator(1).Continue();
}
yield return Yield.It;
try
{
app.MyGames[0].EnterGame();
}
catch (Exception e)
{
Console.WriteLine("Failed to go to game tests");
Console.WriteLine(e);
}
/*Game newGame = Game.NewGame();
yield return new WaitForSecondsEnumerator(5).Continue(); // wait for sync
newGame.EnterGame();*/
@ -167,7 +157,6 @@ namespace TechbloxModdingAPI.Tests
};
for (var index = 0; index < testTypesToRun.Length; index++)
{
Logging.MetaLog($"Running test type {testTypesToRun[index]}");
foreach (Type t in testTypes)
{
foreach (MethodBase m in t.GetMethods())
@ -200,26 +189,16 @@ namespace TechbloxModdingAPI.Tests
cont = false;
}
if (cont)
yield return enumerator.Current;
yield return Yield.It;
} while (cont);
}
yield return new WaitForSecondsEnumerator(1f).Continue();
yield return Yield.It;
}
}
if (index + 1 < testTypesToRun.Length) //Don't toggle on the last test
{
bool running = currentGame.IsTimeRunning;
currentGame.ToggleTimeMode();
while (running ? !currentGame.IsTimeStopped : !currentGame.IsTimeRunning)
{
Logging.MetaLog($"Waiting for time to {(running?"stop":"start")}...");
yield return new WaitForSecondsEnumerator(1).Continue();
}
}
yield return new WaitForSecondsEnumerator(5).Continue();
}
// exit game

View file

@ -1,5 +1,7 @@
using System;
using BepInEx.Bootstrap;
using IllusionInjector;
using IllusionPlugin;
namespace TechbloxModdingAPI.Utility
{
@ -13,11 +15,11 @@ namespace TechbloxModdingAPI.Utility
/// </summary>
/// <returns>The plugin.</returns>
/// <param name="name">The plugin's name.</param>
public static BepInEx.PluginInfo GetPlugin(string name)
public static IPlugin GetPlugin(string name)
{
foreach(var plugin in Chainloader.PluginInfos.Values)
foreach(IPlugin plugin in PluginManager.Plugins)
{
if (plugin.Metadata.Name == name)
if (plugin.Name == name)
{
return plugin;
}
@ -33,15 +35,45 @@ namespace TechbloxModdingAPI.Utility
/// <param name="name">The plugin's name.</param>
public static Version GetPluginVersion(string name)
{
var plugin = GetPlugin(name);
IPlugin plugin = GetPlugin(name);
if (plugin != null) {
try
{
return plugin.Metadata.Version;
} catch (Exception e) when (e is ArgumentException or FormatException or OverflowException) {}
return new Version(plugin.Version);
} catch (Exception e) when (
e is ArgumentException
|| e is ArgumentNullException
|| e is ArgumentOutOfRangeException
|| e is FormatException
|| e is OverflowException) {}
return plugin.GetType().Assembly.GetName().Version;
}
return null;
}
// (I'm leaving the auto-generated version)
// <summary>
// Hell the specified name and version.
// </summary>
// <returns>The hell.</returns>
// <param name="name">Name.</param>
// <param name="version">Version.</param>
/// <summary>
/// Detect if you're in dependency hell with respect to the plugin.
/// ie Check if the plugin doesn't exist or is out of date.
/// When version is null, this only checks if the plugin exists.
/// The version is retrieved using GetPluginVersion(string name).
/// </summary>
/// <returns>Are you in dependency hell?</returns>
/// <param name="name">The plugin's name'</param>
/// <param name="version">The target version.</param>
public static bool Hell(string name, Version version = null)
{
Version pluginVersion = GetPluginVersion(name);
if (version == null) {
return pluginVersion == null;
}
return (pluginVersion == null || pluginVersion < version);
}
}
}

View file

@ -1,75 +0,0 @@
using Svelto.ECS;
using Svelto.ECS.Hybrid;
namespace TechbloxModdingAPI.Utility.ECS
{
public static class ManagedApiExtensions
{
/// <summary>
/// Attempts to query an entity and returns an optional that contains the result if succeeded.
/// <b>This overload does not take initializer data into account.</b>
/// </summary>
/// <param name="entitiesDB">The entities DB</param>
/// <param name="egid">The EGID to query</param>
/// <typeparam name="T">The component type to query</typeparam>
/// <returns>An optional that contains the result on success or is empty if not found</returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EGID egid)
where T : struct, IEntityViewComponent
{
return entitiesDB.TryQueryEntitiesAndIndex<T>(egid, out uint index, out var array)
? new OptionalRef<T>(array, index)
: new OptionalRef<T>();
}
/// <summary>
/// Attempts to query an entity and returns the result in an optional reference.
/// </summary>
/// <param name="entitiesDB">The entities DB to query from</param>
/// <param name="obj">The ECS object to query</param>
/// <param name="group">The group of the entity if the object can have multiple</param>
/// <typeparam name="T">The component to query</typeparam>
/// <returns>A reference to the component or a dummy value</returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj,
ExclusiveGroupStruct group = default)
where T : struct, IEntityViewComponent
{
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
var opt = QueryEntityOptional<T>(entitiesDB, id);
return opt ? opt : new OptionalRef<T>(obj, false);
}
/// <summary>
/// Attempts to query an entity and returns the result or a dummy value that can be modified.
/// </summary>
/// <param name="entitiesDB">The entities DB to query from</param>
/// <param name="obj">The ECS object to query</param>
/// <param name="group">The group of the entity if the object can have multiple</param>
/// <typeparam name="T">The component to query</typeparam>
/// <returns>A reference to the component or a dummy value</returns>
public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj,
ExclusiveGroupStruct group = default)
where T : struct, IEntityViewComponent
{
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
var opt = QueryEntityOptional<T>(entitiesDB, id);
if (opt) return ref opt.Get();
if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrAdd<T>();
return ref opt.Get(); //Default value
}
/// <summary>
/// Query entities as OptionalRefs. The elements always exist, it's just a nice way to encapsulate the data.
/// </summary>
/// <param name="entitiesDB"></param>
/// <param name="group"></param>
/// <param name="select"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TR"></typeparam>
/// <returns></returns>
public static RefCollection<T> QueryEntitiesOptional<T>(this EntitiesDB entitiesDB, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent
{
var (buffer, ids, count) = entitiesDB.QueryEntities<T>(group);
return new RefCollection<T>(count, buffer, ids, group);
}
}
}

View file

@ -1,56 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using Svelto.DataStructures;
using Svelto.ECS;
namespace TechbloxModdingAPI.Utility.ECS
{
public static partial class NativeApiExtensions
{
[SuppressMessage("ReSharper", "StaticMemberInGenericType")]
private static class EntitiesDBHelper<T> where T : unmanaged, IEntityComponent
{ // Each type gets a new set of fields here (that's what the ReSharper warning is about too)
public static readonly Lazy<MethodInfo> EntityStream =
new(() => AccessTools.PropertyGetter(typeof(EntitiesDB), "_entityStream"));
public static readonly Lazy<FieldInfo> Streams = new(() =>
AccessTools.Field(EntityStream.Value.ReturnType, "_streams"));
public static readonly Lazy<FieldInfo> Consumers = new(() =>
AccessTools.Field(typeof(EntityStream<T>), "_consumers"));
public static readonly Lazy<MethodInfo> TryGetValue =
new(AccessTools.Method(Streams.Value.FieldType, "TryGetValue"));
public static readonly Lazy<FieldInfo> RingBuffer =
new(() => AccessTools.Field(typeof(Consumer<T>), "_ringBuffer"));
}
private static EntityStream<T> GetEntityStream<T>(this EntitiesDB entitiesDB) where T : unmanaged, IEntityComponent
{
// EntitiesStreams (internal)
var entitiesStreams = EntitiesDBHelper<T>.EntityStream.Value.Invoke(entitiesDB, Array.Empty<object>());
// FasterDictionary<RefWrapperType, ITypeSafeStream> (interface is internal)
var streams = EntitiesDBHelper<T>.Streams.Value.GetValue(entitiesStreams);
var parameters = new object[] { TypeRefWrapper<T>.wrapper, null };
var success = EntitiesDBHelper<T>.TryGetValue.Value.Invoke(streams, parameters);
if (!(bool)success)
return null; // There is no entity stream for this type
return (EntityStream<T>)parameters[1];
}
private static ThreadSafeFasterList<Consumer<T>> GetConsumers<T>(this EntityStream<T> stream) where T : unmanaged, IEntityComponent
{
return (ThreadSafeFasterList<Consumer<T>>)EntitiesDBHelper<T>.Consumers.Value.GetValue(stream);
}
private static RingBuffer<(T, EGID)> GetRingBuffer<T>(this Consumer<T> consumer) where T : unmanaged, IEntityComponent
{
return (RingBuffer<(T, EGID)>)EntitiesDBHelper<T>.RingBuffer.Value.GetValue(consumer);
}
}
}

View file

@ -1,129 +0,0 @@
using System;
using System.Collections.Generic;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.Tasks;
using Svelto.Tasks.Lean;
using TechbloxModdingAPI.Tasks;
namespace TechbloxModdingAPI.Utility.ECS
{
public static partial class NativeApiExtensions
{
/// <summary>
/// Attempts to query an entity and returns an optional that contains the result if succeeded.
/// <b>This overload does not take initializer data into account.</b>
/// </summary>
/// <param name="entitiesDB">The entities DB</param>
/// <param name="egid">The EGID to query</param>
/// <typeparam name="T">The component type to query</typeparam>
/// <returns>An optional that contains the result on success or is empty if not found</returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EGID egid)
where T : unmanaged, IEntityComponent
{
return entitiesDB.TryQueryEntitiesAndIndex<T>(egid, out uint index, out var array)
? new OptionalRef<T>(array, index)
: new OptionalRef<T>();
}
/// <summary>
/// Attempts to query an entity and returns the result in an optional reference.
/// </summary>
/// <param name="entitiesDB">The entities DB to query from</param>
/// <param name="obj">The ECS object to query</param>
/// <param name="group">The group of the entity if the object can have multiple</param>
/// <typeparam name="T">The component to query</typeparam>
/// <returns>A reference to the component or a dummy value</returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj,
ExclusiveGroupStruct group = default)
where T : unmanaged, IEntityComponent
{
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
var opt = QueryEntityOptional<T>(entitiesDB, id);
return opt ? opt : new OptionalRef<T>(obj, true);
}
/// <summary>
/// Attempts to query an entity and returns the result or a dummy value that can be modified.
/// </summary>
/// <param name="entitiesDB">The entities DB to query from</param>
/// <param name="obj">The ECS object to query</param>
/// <param name="group">The group of the entity if the object can have multiple</param>
/// <typeparam name="T">The component to query</typeparam>
/// <returns>A reference to the component or a dummy value</returns>
public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj,
ExclusiveGroupStruct group = default)
where T : unmanaged, IEntityComponent
{
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
var opt = QueryEntityOptional<T>(entitiesDB, id);
if (opt) return ref opt.Get();
if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrAdd<T>();
/*if (!obj.InitData.Valid) return ref opt.Get(); //Default value
var init = obj.InitData.Initializer(id);
// Do not create the component if missing, as that can trigger Add() listeners that, in some cases, may be
// invalid if (ab)using the classes in an unusual way - TODO: Check entity descriptor or something
if (init.Has<T>()) return ref init.Get<T>();*/
return ref opt.Get(); //Default value
}
/// <summary>
/// Publishes an entity change, ignoring duplicate publishes and delaying changes as necessary.
/// It will only publish in the next frame.
/// </summary>
/// <param name="entitiesDB">The entities DB to publish to</param>
/// <param name="id">The ECS object that got changed</param>
/// <typeparam name="T">The component that changed</typeparam>
public static void PublishEntityChangeDelayed<T>(this EntitiesDB entitiesDB, EGID id)
where T : unmanaged, IEntityComponent
{
PublishChanges<T>(entitiesDB, id).RunOn(Scheduler.leanRunner);
}
private static IEnumerator<TaskContract> PublishChanges<T>(EntitiesDB entitiesDB, EGID id)
where T : unmanaged, IEntityComponent
{
yield return Yield.It;
var entityStream = entitiesDB.GetEntityStream<T>();
if (entityStream is null)
yield break; // There is no entity stream for this type
var consumers = entityStream.GetConsumers();
if (consumers == null)
{
Console.WriteLine("Consumers is null");
yield break;
}
bool waitForConsumers;
do
{
waitForConsumers = false;
for (int i = 0; i < consumers.count; i++)
{
var buffer = consumers[i].GetRingBuffer();
if (buffer.Count + 1 <= buffer.Capacity) continue;
waitForConsumers = true;
break;
}
if (waitForConsumers) yield return Yield.It;
} while (waitForConsumers);
entitiesDB.PublishEntityChange<T>(id);
}
/// <summary>
/// Query entities as OptionalRefs. The elements always exist, it's just a nice way to encapsulate the data.
/// </summary>
/// <param name="entitiesDB"></param>
/// <param name="group"></param>
/// <param name="select"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TR"></typeparam>
/// <returns></returns>
public static RefCollection<T> QueryEntitiesOptional<T>(this EntitiesDB entitiesDB, ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent
{
var (buffer, ids, count) = entitiesDB.QueryEntities<T>(group);
return new RefCollection<T>(count, buffer, ids, group);
}
}
}

View file

@ -0,0 +1,43 @@
using System;
using TechbloxModdingAPI.Events;
namespace TechbloxModdingAPI.Utility
{
public static class ExceptionUtil
{
/// <summary>
/// Invokes an event with a null-check.
/// </summary>
/// <param name="handler">The event to emit, can be null</param>
/// <param name="sender">Event sender</param>
/// <param name="args">Event arguments</param>
/// <typeparam name="T">Type of the event arguments</typeparam>
public static void InvokeEvent<T>(EventHandler<T> handler, object sender, T args)
{
handler?.Invoke(sender, args);
}
/// <summary>
/// Wraps the event handler in a try-catch block to avoid propagating exceptions.
/// </summary>
/// <param name="handler">The handler to wrap (not null)</param>
/// <typeparam name="T">Type of the event arguments</typeparam>
/// <returns>The wrapped handler</returns>
public static EventHandler<T> WrapHandler<T>(EventHandler<T> handler)
{
return (sender, e) =>
{
try
{
handler(sender, e);
}
catch (Exception e1)
{
EventRuntimeException wrappedException =
new EventRuntimeException($"EventHandler with arg type {typeof(T).Name} threw an exception", e1);
Logging.LogWarning(wrappedException.ToString());
}
};
}
}
}

View file

@ -1,14 +1,20 @@
using DataLoader;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataLoader;
using HarmonyLib;
using RobocraftX;
using RobocraftX.CR.MainGame;
using RobocraftX.Common.Utilities;
using RobocraftX.GUI;
using RobocraftX.Multiplayer;
using RobocraftX.Rendering;
using Svelto.Context;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.GUI;
using Techblox.GameSelection;
using Svelto.ECS.Schedulers;
using UnityEngine;
using Unity.Entities;
using Unity.Physics.Systems;
@ -66,6 +72,14 @@ namespace TechbloxModdingAPI.Utility
}
}
public static SimpleEntitiesSubmissionScheduler _mainGameSubmissionScheduler
{
get
{
return (SimpleEntitiesSubmissionScheduler)fgcr?.Field("_sub").Field("_mainGameSubmissionScheduler").GetValue();
}
}
public static BuildPhysicsWorld _physicsWorldSystem
{
get
@ -98,6 +112,14 @@ namespace TechbloxModdingAPI.Utility
}
}
public static PhysicsUtility _physicsUtility
{
get
{
return (PhysicsUtility)fgcr?.Field("_physicsUtility").GetValue();
}
}
/*public static UnityEntitySubmissionScheduler _frontEndSubmissionScheduler
{
get
@ -122,11 +144,11 @@ namespace TechbloxModdingAPI.Utility
}
}
public static ECSMainGameResourceManagers _managers
public static ECSGameObjectResourceManager _eCsGameObjectResourceManager
{
get
{
return (ECSMainGameResourceManagers)fgcr?.Field("_gameManagers").GetValue();
return (ECSGameObjectResourceManager)fgcr?.Field("_eCsGameObjectResourceManager").GetValue();
}
}
@ -146,22 +168,6 @@ namespace TechbloxModdingAPI.Utility
}
}
public static SveltoGUI _frontEndGUI
{
get
{
return (SveltoGUI)fgcr?.Field("_frontEndGUI").GetValue();
}
}
public static GameSelectionData _gameSelectionData
{
get
{
return (GameSelectionData)fgcr?.Field("_gameSelectionData").GetValue();
}
}
private static Traverse fgcr;
public static void Init(FullGameCompositionRoot instance)

View file

@ -1,5 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RobocraftX.StateSync;
using Svelto.ECS;
@ -23,10 +26,10 @@ namespace TechbloxModdingAPI.Utility
{
Logging.MetaDebugLog($"Registering Game IApiEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine);
if (engine is IFactoryEngine factoryEngine)
factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory();
if (engine is IFunEngine funEngine)
funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions();
if (typeof(IFactoryEngine).IsAssignableFrom(engine.GetType()))
{
((IFactoryEngine)engine).Factory = _lastEngineRoot.GenerateEntityFactory();
}
}
}
@ -63,7 +66,6 @@ namespace TechbloxModdingAPI.Utility
var enginesRoot = helper.enginesRoot;
_lastEngineRoot = enginesRoot;
IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions();
foreach (var key in _gameEngines.Keys)
{
Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}");
@ -73,8 +75,6 @@ namespace TechbloxModdingAPI.Utility
enginesRoot.AddEngine(_gameEngines[key]);
if (_gameEngines[key] is IFactoryEngine factEngine)
factEngine.Factory = factory;
if (_gameEngines[key] is IFunEngine funEngine)
funEngine.Functions = functions;
}
}
}

View file

@ -163,7 +163,7 @@ namespace TechbloxModdingAPI.Utility
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CommandLog(string msg)
{
Log(msg);
uREPL.Log.Output(msg);
}
/// <summary>
@ -179,7 +179,7 @@ namespace TechbloxModdingAPI.Utility
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CommandLogError(string msg)
{
LogError(msg);
uREPL.Log.Error(msg);
}
/// <summary>
@ -195,7 +195,7 @@ namespace TechbloxModdingAPI.Utility
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CommandLogWarning(string msg)
{
LogWarning(msg);
uREPL.Log.Warn(msg);
}
}

View file

@ -0,0 +1,55 @@
using Svelto.ECS;
using Svelto.ECS.Hybrid;
using TechbloxModdingAPI.Blocks;
namespace TechbloxModdingAPI.Utility
{
public static class ManagedApiExtensions
{
/// <summary>
/// Attempts to query an entity and returns an optional that contains the result if succeeded.
/// <b>This overload does not take initializer data into account.</b>
/// </summary>
/// <param name="entitiesDB">The entities DB</param>
/// <param name="egid">The EGID to query</param>
/// <typeparam name="T">The component type to query</typeparam>
/// <returns>An optional that contains the result on success or is empty if not found</returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EGID egid)
where T : struct, IEntityViewComponent
{
return entitiesDB.TryQueryEntitiesAndIndex<T>(egid, out uint index, out var array)
? new OptionalRef<T>(array, index)
: new OptionalRef<T>();
}
/// <summary>
/// Attempts to query an entity and returns the result or a dummy value that can be modified.
/// </summary>
/// <param name="entitiesDB"></param>
/// <param name="obj"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj)
where T : struct, IEntityViewComponent
{
var opt = QueryEntityOptional<T>(entitiesDB, obj.Id);
return opt ? opt : new OptionalRef<T>(obj, true);
}
/// <summary>
/// Attempts to query an entity and returns the result or a dummy value that can be modified.
/// </summary>
/// <param name="entitiesDB"></param>
/// <param name="obj"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj)
where T : struct, IEntityViewComponent
{
var opt = QueryEntityOptional<T>(entitiesDB, obj.Id);
if (opt) return ref opt.Get();
if (obj.InitData.Valid) return ref obj.InitData.Initializer(obj.Id).GetOrCreate<T>();
return ref opt.Get(); //Default value
}
}
}

View file

@ -26,10 +26,10 @@ namespace TechbloxModdingAPI.Utility
{
Logging.MetaDebugLog($"Registering Menu IApiEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine);
if (engine is IFactoryEngine factoryEngine)
factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory();
if (engine is IFunEngine funEngine)
funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions();
if (typeof(IFactoryEngine).IsAssignableFrom(engine.GetType()))
{
((IFactoryEngine)engine).Factory = _lastEngineRoot.GenerateEntityFactory();
}
}
}
@ -65,13 +65,14 @@ namespace TechbloxModdingAPI.Utility
{
_lastEngineRoot = enginesRoot;
IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions();
foreach (var key in _menuEngines.Keys)
{
Logging.MetaDebugLog($"Registering Menu IApiEngine {_menuEngines[key].Name}");
enginesRoot.AddEngine(_menuEngines[key]);
if (_menuEngines[key] is IFactoryEngine factEngine) factEngine.Factory = factory;
if(_menuEngines[key] is IFunEngine funEngine) funEngine.Functions = functions;
if (_menuEngines[key] is IFactoryEngine factEngine)
{
factEngine.Factory = factory;
}
}
}
}

View file

@ -0,0 +1,53 @@
using Svelto.ECS;
namespace TechbloxModdingAPI.Utility
{
public static class NativeApiExtensions
{
/// <summary>
/// Attempts to query an entity and returns an optional that contains the result if succeeded.
/// <b>This overload does not take initializer data into account.</b>
/// </summary>
/// <param name="entitiesDB">The entities DB</param>
/// <param name="egid">The EGID to query</param>
/// <typeparam name="T">The component type to query</typeparam>
/// <returns>An optional that contains the result on success or is empty if not found</returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EGID egid)
where T : unmanaged, IEntityComponent
{
return entitiesDB.TryQueryEntitiesAndIndex<T>(egid, out uint index, out var array)
? new OptionalRef<T>(array, index)
: new OptionalRef<T>();
}
/// <summary>
/// Attempts to query an entity and returns the result or a dummy value that can be modified.
/// </summary>
/// <param name="entitiesDB"></param>
/// <param name="obj"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj)
where T : unmanaged, IEntityComponent
{
var opt = QueryEntityOptional<T>(entitiesDB, obj.Id);
return opt ? opt : new OptionalRef<T>(obj, true);
}
/// <summary>
/// Attempts to query an entity and returns the result or a dummy value that can be modified.
/// </summary>
/// <param name="entitiesDB"></param>
/// <param name="obj"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj)
where T : unmanaged, IEntityComponent
{
var opt = QueryEntityOptional<T>(entitiesDB, obj.Id);
if (opt) return ref opt.Get();
if (obj.InitData.Valid) return ref obj.InitData.Initializer(obj.Id).GetOrCreate<T>();
return ref opt.Get(); //Default value
}
}
}

View file

@ -1,12 +1,12 @@
using System;
using System.Runtime.InteropServices;
using Svelto.DataStructures;
using Svelto.ECS;
namespace TechbloxModdingAPI.Utility
{
public ref struct OptionalRef<T> where T : struct, IBaseEntityComponent
public ref struct OptionalRef<T> where T : struct, IEntityComponent
{
private readonly EGID entityId;
private readonly State state;
private readonly uint index;
private NB<T> array;
@ -14,22 +14,19 @@ namespace TechbloxModdingAPI.Utility
private readonly EntityInitializer initializer;
//The possible fields are: (index && (array || managedArray)) || initializer
public OptionalRef(NB<T> array, uint index, EGID entityId = default)
public OptionalRef(NB<T> array, uint index)
{
state = State.Native;
this.array = array;
this.index = index;
this.entityId = entityId;
initializer = default;
managedArray = default;
}
public OptionalRef(MB<T> array, uint index, EGID entityId = default)
public OptionalRef(MB<T> array, uint index)
{
state = State.Managed;
managedArray = array;
this.index = index;
this.entityId = entityId;
initializer = default;
this.array = default;
}
@ -39,9 +36,8 @@ namespace TechbloxModdingAPI.Utility
/// </summary>
/// <param name="obj">The object with the initializer</param>
/// <param name="unmanaged">Whether the struct is unmanaged</param>
public OptionalRef(EcsObjectBase obj, bool unmanaged, EGID entityId = default)
public OptionalRef(EcsObjectBase obj, bool unmanaged)
{
this.entityId = entityId;
if (obj.InitData.Valid)
{
initializer = obj.InitData.Initializer(obj.Id);
@ -54,7 +50,6 @@ namespace TechbloxModdingAPI.Utility
}
array = default;
index = default;
managedArray = default;
}
/// <summary>
@ -63,40 +58,22 @@ namespace TechbloxModdingAPI.Utility
/// <returns>The value or the default value</returns>
public ref T Get()
{
CompRefCache.Default = default; //The default value can be changed by mods
if (state == State.Empty) return ref CompRefCache.Default;
if ((state & State.Initializer) != State.Empty) return ref initializer.GetOrAdd<T>();
if ((state & State.Initializer) != State.Empty) return ref initializer.GetOrCreate<T>();
if ((state & State.Native) != State.Empty) return ref array[index];
return ref managedArray[index];
}
/// <summary>
/// A non-by-ref view that allows getting and setting the value.
/// </summary>
public T Value
{
get => Get();
set => Get() = value;
}
/// <summary>
/// The ID of the entity this component belongs to or default if it doesn't exist.
/// </summary>
public EGID EGID => entityId;
public bool Exists => state != State.Empty;
public T? Nullable() => this ? Get() : default;
public static implicit operator T(OptionalRef<T> opt) => opt.Get();
public static implicit operator bool(OptionalRef<T> opt) => opt.state != State.Empty;
public static implicit operator EGID(OptionalRef<T> opt) => opt.entityId;
/// <summary>
/// Creates an instance of a struct T that can be referenced.
/// </summary>
private struct CompRefCache
internal struct CompRefCache
{
public static T Default;
}

View file

@ -1,94 +0,0 @@
using System;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.Hybrid;
using Svelto.ECS.Internal;
namespace TechbloxModdingAPI.Utility
{
public readonly ref struct RefCollection<T> where T : struct, IBaseEntityComponent
{
private readonly bool managed;
private readonly int count;
private readonly NB<T> nativeArray;
private readonly MB<T> managedArray;
private readonly NativeEntityIDs nativeIDs;
private readonly ManagedEntityIDs managedIDs;
private readonly ExclusiveGroupStruct group;
public RefCollection(int count, MB<T> managedArray, ManagedEntityIDs managedIDs, ExclusiveGroupStruct group)
{
this.count = count;
this.managedArray = managedArray;
this.managedIDs = managedIDs;
this.group = group;
managed = true;
nativeArray = default;
nativeIDs = default;
}
public RefCollection(int count, NB<T> nativeArray, NativeEntityIDs nativeIDs, ExclusiveGroupStruct group)
{
this.count = count;
this.nativeArray = nativeArray;
this.nativeIDs = nativeIDs;
this.group = group;
managed = false;
managedArray = default;
managedIDs = default;
}
public Enumerator GetEnumerator() => new(this);
/// <summary>
/// The amount of items in the collection.
/// </summary>
public int Count => count;
public T[] ToArray() => ToArray(a => a.Component);
public TA[] ToArray<TA>(Func<(T Component, EGID ID), TA> transformFunction, Predicate<(T Component, EGID ID)> predicateFunction = null)
{
var result = new TA[Count];
int i = 0;
foreach (var opt in this)
{
if (predicateFunction != null && !predicateFunction((opt.Get(), opt.EGID))) continue;
result[i] = transformFunction((opt.Get(), opt.EGID));
i++;
}
return result;
}
public ref struct Enumerator
{
private RefCollection<T> coll;
private int index;
public Enumerator(RefCollection<T> collection)
{
index = -1;
coll = collection;
}
public OptionalRef<T> Current
{
get
{
if (coll.count <= index && index >= 0) return default;
if (coll.managed)
return new OptionalRef<T>(coll.managedArray, (uint)index,
new EGID(coll.managedIDs[index], coll.group));
return new OptionalRef<T>(coll.nativeArray, (uint)index,
new EGID(coll.nativeIDs[index], coll.group));
}
}
public bool MoveNext()
{
return ++index < coll.count;
}
}
}
}

View file

@ -1,105 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace TechbloxModdingAPI.Utility
{
public class WeakDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TValue : class
{
private readonly Dictionary<TKey, WeakReference<TValue>> _dictionary = new();
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
using var enumerator = _dictionary.GetEnumerator();
while (enumerator.MoveNext())
{
if (enumerator.Current.Value.TryGetTarget(out var value))
yield return new KeyValuePair<TKey, TValue>(enumerator.Current.Key, value);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
public void Clear() => _dictionary.Clear();
public bool Contains(KeyValuePair<TKey, TValue> item) =>
TryGetValue(item.Key, out var value) && item.Value == value;
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) =>
throw new System.NotImplementedException();
public bool Remove(KeyValuePair<TKey, TValue> item) => Contains(item) && Remove(item.Key);
public int Count => _dictionary.Count;
public bool IsReadOnly => false;
public bool ContainsKey(TKey key) => TryGetValue(key, out _);
public void Add(TKey key, TValue value) => _dictionary.Add(key, new WeakReference<TValue>(value));
public bool Remove(TKey key) => _dictionary.Remove(key);
public bool TryGetValue(TKey key, out TValue value)
{
value = null;
bool ret = _dictionary.TryGetValue(key, out var reference) && reference.TryGetTarget(out value);
if (!ret) _dictionary.Remove(key);
return ret;
}
public TValue this[TKey key]
{
get => TryGetValue(key, out var value)
? value
: throw new KeyNotFoundException($"Key {key} not found in WeakDictionary.");
set => _dictionary[key] = new WeakReference<TValue>(value);
}
public ICollection<TKey> Keys => new KeyCollection(this);
public ICollection<TValue> Values => new ValueCollection(this);
public class KeyCollection : ICollection<TKey>, IReadOnlyCollection<TKey>
{
private readonly WeakDictionary<TKey, TValue> _dictionary;
internal KeyCollection(WeakDictionary<TKey, TValue> dictionary)
{
_dictionary = dictionary;
}
public IEnumerator<TKey> GetEnumerator()
{
using var enumerator = _dictionary.GetEnumerator();
while (enumerator.MoveNext())
yield return enumerator.Current.Key;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(TKey item) => throw new NotSupportedException("The key collection is read only.");
public void Clear() => throw new NotSupportedException("The key collection is read only.");
public bool Contains(TKey item) => _dictionary.ContainsKey(item);
public void CopyTo(TKey[] array, int arrayIndex) => throw new NotImplementedException();
public bool Remove(TKey item) => throw new NotSupportedException("The key collection is read only.");
public int Count => _dictionary.Count;
public bool IsReadOnly => true;
}
public class ValueCollection : ICollection<TValue>, IReadOnlyCollection<TValue>
{
private readonly WeakDictionary<TKey, TValue> _dictionary;
internal ValueCollection(WeakDictionary<TKey, TValue> dictionary)
{
_dictionary = dictionary;
}
public IEnumerator<TValue> GetEnumerator()
{
using var enumerator = _dictionary.GetEnumerator();
while (enumerator.MoveNext())
yield return enumerator.Current.Value;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(TValue item) => throw new NotSupportedException("The value collection is read only.");
public void Clear() => throw new NotSupportedException("The value collection is read only.");
public bool Contains(TValue item) => _dictionary.Any(kv => kv.Value == item);
public void CopyTo(TValue[] array, int arrayIndex) => throw new NotImplementedException();
public bool Remove(TValue item) => throw new NotSupportedException("The value collection is read only.");
public int Count => _dictionary.Count;
public bool IsReadOnly => true;
}
}
}

View file

@ -1,59 +0,0 @@
using System;
using System.Collections.Generic;
using TechbloxModdingAPI.Events;
namespace TechbloxModdingAPI.Utility
{
/// <summary>
/// Wraps the event handler in a try-catch block to avoid propagating exceptions.
/// </summary>
/// <typeparam name="T">The event arguments type</typeparam>
public struct WrappedHandler<T>
{
private EventHandler<T> eventHandler;
/// <summary>
/// Store wrappers so we can unregister them properly
/// </summary>
private static Dictionary<EventHandler<T>, EventHandler<T>> wrappers =
new Dictionary<EventHandler<T>, EventHandler<T>>();
public static WrappedHandler<T> operator +(WrappedHandler<T> original, EventHandler<T> added)
{
EventHandler<T> wrapped = (sender, e) =>
{
try
{
added(sender, e);
}
catch (Exception e1)
{
EventRuntimeException wrappedException =
new EventRuntimeException($"EventHandler with arg type {typeof(T).Name} threw an exception",
e1);
Logging.LogWarning(wrappedException.ToString());
}
};
if (wrappers.ContainsKey(added))
{
original.eventHandler -= wrapped;
wrappers.Remove(added);
}
wrappers.Add(added, wrapped);
return new WrappedHandler<T> { eventHandler = original.eventHandler + wrapped };
}
public static WrappedHandler<T> operator -(WrappedHandler<T> original, EventHandler<T> removed)
{
if (!wrappers.TryGetValue(removed, out var wrapped)) return original;
wrappers.Remove(removed);
return new WrappedHandler<T> { eventHandler = original.eventHandler - wrapped };
}
public void Invoke(object sender, T args)
{
eventHandler?.Invoke(sender, args);
}
}
}

Some files were not shown because too many files have changed in this diff Show more