Compare commits

..

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

99 changed files with 2440 additions and 8258 deletions

View file

@ -5,7 +5,7 @@ import re
# this assumes a mostly semver-complient version number # this assumes a mostly semver-complient version number
if __name__ == "__main__": 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).") 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() args = parser.parse_args()
@ -28,12 +28,12 @@ if __name__ == "__main__":
old_version = "" old_version = ""
new_version = "" new_version = ""
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "r") as xmlFile: with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "r") as xmlFile:
print("Parsing TechbloxModdingAPI.csproj") print("Parsing GamecraftModdingAPI.csproj")
fileStr = xmlFile.read() fileStr = xmlFile.read()
versionMatch = re.search(r"<Version>(.+)</Version>", fileStr) versionMatch = re.search(r"<Version>(.+)</Version>", fileStr)
if versionMatch is None: if versionMatch is None:
print("Unable to find version number in TechbloxModdingAPI.csproj") print("Unable to find version number in GamecraftModdingAPI.csproj")
exit(1) exit(1)
old_version = versionMatch.group(1) old_version = versionMatch.group(1)
versionList = old_version.split(".") versionList = old_version.split(".")
@ -53,7 +53,7 @@ if __name__ == "__main__":
print(new_version) print(new_version)
newFileContents = fileStr.replace("<Version>"+old_version+"</Version>", "<Version>"+new_version+"</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") print("Writing new version to project file")
xmlFile.write(newFileContents) xmlFile.write(newFileContents)

View file

@ -5,7 +5,7 @@ from pathlib import Path, PurePath
import re import re
import os 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): def getAssemblyReferences(path):
asmDir = Path(path) asmDir = Path(path)
@ -15,12 +15,10 @@ def getAssemblyReferences(path):
addedPath = "../" addedPath = "../"
asmDir = Path(addedPath + path) asmDir = Path(addedPath + path)
for child in asmDir.iterdir(): 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"): if child.is_file() and re.search(DLL_EXCLUSIONS_REGEX, str(child), re.I) is None and str(child).lower().endswith(".dll"):
childstr = str(child) childstr = str(child)
childstr = os.path.relpath(childstr, addedPath).replace("\\", "/") childstr = os.path.relpath(childstr, addedPath).replace("\\", "/")
result.append(childstr) result.append(childstr)
result.sort(key=str.lower)
result = [path + "/IllusionInjector.dll", path + "/IllusionPlugin.dll"] + result # Always put it on top
return result return result
def buildReferencesXml(path): def buildReferencesXml(path):
@ -41,7 +39,7 @@ if __name__ == "__main__":
args = parser.parse_args() args = parser.parse_args()
print("Building Assembly references") print("Building Assembly references")
asmXml = buildReferencesXml("../ref_TB/Techblox_Data/Managed") asmXml = buildReferencesXml("../ref/TechbloxPreview_Data/Managed")
# print(asmXml) # print(asmXml)
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "r") as xmlFile: with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "r") as xmlFile:
@ -53,7 +51,7 @@ if __name__ == "__main__":
if depsStart is None or depsEnd is None: if depsStart is None or depsEnd is None:
print("Unable to find dependency XML comments, aborting!") print("Unable to find dependency XML comments, aborting!")
exit(1) 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: with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "w") as xmlFile:
print("Writing Assembly references") print("Writing Assembly references")
xmlFile.write(newFileStr) 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 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechbloxModdingAPI", "TechbloxModdingAPI\TechbloxModdingAPI.csproj", "{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechbloxModdingAPI", "TechbloxModdingAPI\TechbloxModdingAPI.csproj", "{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Test|Any CPU
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Test|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE 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,62 @@
using System;
using RobocraftX.GUI.MyGamesScreen;
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> /// </summary>
public class Client 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 Func<object> ErrorHandlerInstanceGetter;
protected static Action<object, Error> EnqueueError; protected static Action<object, Error> EnqueueError;
/// <summary> protected static Action<object> HandleErrorClosed;
/// <summary>
/// An event that fires whenever the main menu is loaded. /// An event that fires whenever the main menu is loaded.
/// </summary> /// </summary>
public static event EventHandler<MenuEventArgs> EnterMenu public static event EventHandler<MenuEventArgs> EnterMenu
{ {
add => Game.menuEngine.EnterMenu += value; add => appEngine.EnterMenu += ExceptionUtil.WrapHandler(value);
remove => Game.menuEngine.EnterMenu -= value; remove => appEngine.EnterMenu -= value;
} }
/// <summary> /// <summary>
@ -34,8 +37,8 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<MenuEventArgs> ExitMenu public static event EventHandler<MenuEventArgs> ExitMenu
{ {
add => Game.menuEngine.ExitMenu += value; add => appEngine.ExitMenu += ExceptionUtil.WrapHandler(value);
remove => Game.menuEngine.ExitMenu -= value; remove => appEngine.ExitMenu -= value;
} }
/// <summary> /// <summary>
@ -66,8 +69,8 @@ namespace TechbloxModdingAPI.App
{ {
get get
{ {
if (!Game.menuEngine.IsInMenu) return Array.Empty<Game>(); if (!appEngine.IsInMenu) return new Game[0];
return Game.menuEngine.GetMyGames(); 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> /// <value><c>true</c> if in menu; <c>false</c> when loading or in a game.</value>
public bool InMenu public bool InMenu
{ {
get => Game.menuEngine.IsInMenu; get => appEngine.IsInMenu;
} }
/// <summary> /// <summary>
@ -93,26 +96,14 @@ namespace TechbloxModdingAPI.App
EnqueueError(errorHandlerInstance, popup); 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(); object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance); HandleErrorClosed(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();
}
internal static void Init() internal static void Init()
{ {
@ -125,6 +116,11 @@ namespace TechbloxModdingAPI.App
EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError") EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError")
.MakeGenericMethod(errorHandler, errorHandle) .MakeGenericMethod(errorHandler, errorHandle)
.Invoke(null, new object[0]); .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 // Creating delegates once is faster than reflection every time
@ -149,23 +145,14 @@ namespace TechbloxModdingAPI.App
return enqueueCasted; return enqueueCasted;
} }
private static (Action Close, Action FirstButton, Action SecondButton) _errorPopup; private static Action<object> GenHandlePopupClosed<T>()
private static (Action Close, Action FirstButton, Action SecondButton) GetPopupCloseMethods(object handler)
{ {
if (_errorPopup.Close != null) Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
return _errorPopup; MethodInfo handlePopupClosed = AccessTools.Method(errorHandler, "HandlePopupClosed");
Type errorHandler = handler.GetType(); Action<T> handleSimple =
FieldInfo field = AccessTools.Field(errorHandler, "errorPopup"); (Action<T>) Delegate.CreateDelegate(typeof(Action<T>), handlePopupClosed);
var errorPopup = (ErrorPopup)field.GetValue(handler); Action<object> handleCasted = (object instance) => handleSimple((T) instance);
MethodInfo info = AccessTools.Method(errorPopup.GetType(), "ClosePopup"); return handleCasted;
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;
} }
} }
} }

View file

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

View file

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

View file

@ -2,10 +2,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using RobocraftX.Common;
using RobocraftX.GUI.MyGamesScreen; using RobocraftX.GUI.MyGamesScreen;
using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using Techblox.GameSelection;
using TechbloxModdingAPI;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Tasks; using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -21,7 +23,7 @@ namespace TechbloxModdingAPI.App
{ {
// extensible engines // extensible engines
protected static GameGameEngine gameEngine = new GameGameEngine(); 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 DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine();
protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine(); protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine();
@ -91,7 +93,7 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<GameEventArgs> Simulate public static event EventHandler<GameEventArgs> Simulate
{ {
add => buildSimEventEngine.SimulationMode += value; add => buildSimEventEngine.SimulationMode += ExceptionUtil.WrapHandler(value);
remove => buildSimEventEngine.SimulationMode -= value; remove => buildSimEventEngine.SimulationMode -= value;
} }
@ -101,7 +103,7 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<GameEventArgs> Edit public static event EventHandler<GameEventArgs> Edit
{ {
add => buildSimEventEngine.BuildMode += value; add => buildSimEventEngine.BuildMode += ExceptionUtil.WrapHandler(value);
remove => buildSimEventEngine.BuildMode -= value; remove => buildSimEventEngine.BuildMode -= value;
} }
@ -110,7 +112,7 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<GameEventArgs> Enter public static event EventHandler<GameEventArgs> Enter
{ {
add => gameEngine.EnterGame += value; add => gameEngine.EnterGame += ExceptionUtil.WrapHandler(value);
remove => gameEngine.EnterGame -= value; remove => gameEngine.EnterGame -= value;
} }
@ -120,7 +122,7 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<GameEventArgs> Exit public static event EventHandler<GameEventArgs> Exit
{ {
add => gameEngine.ExitGame += value; add => gameEngine.ExitGame += ExceptionUtil.WrapHandler(value);
remove => gameEngine.ExitGame -= value; remove => gameEngine.ExitGame -= value;
} }
@ -163,7 +165,7 @@ namespace TechbloxModdingAPI.App
{ {
if (!VerifyMode()) return null; if (!VerifyMode()) return null;
if (menuMode) return menuEngine.GetGameInfo(EGID).GameName; if (menuMode) return menuEngine.GetGameInfo(EGID).GameName;
return gameEngine.GetGameData().saveName; return GameMode.SaveGameDetails.Name;
} }
set set
@ -172,7 +174,11 @@ namespace TechbloxModdingAPI.App
if (menuMode) if (menuMode)
{ {
menuEngine.SetGameName(EGID, value); 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) if (menuMode)
{ {
menuEngine.SetGameDescription(EGID, value); 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 (!VerifyMode()) return null;
if (menuMode) return menuEngine.GetGameInfo(EGID).SavedGamePath; if (menuMode) return menuEngine.GetGameInfo(EGID).SavedGamePath;
return gameEngine.GetGameData().gameID; return GameMode.SaveGameDetails.Folder;
} }
set set
@ -219,6 +229,11 @@ namespace TechbloxModdingAPI.App
{ {
menuEngine.GetGameInfo(EGID).SavedGamePath.Set(value); 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. /// In most cases this is invalid and returns 0, so this can be ignored.
/// </summary> /// </summary>
/// <value>The workshop identifier.</value> /// <value>The workshop identifier.</value>
[Obsolete]
public ulong WorkshopId public ulong WorkshopId
{ {
get 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 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 get
{ {
if (menuMode || !VerifyMode()) return CurrentGameMode.None; if (menuMode || !VerifyMode()) return CurrentGameMode.None;
return gameEngine.GetGameData().gameMode == GameMode.CreateWorld ? CurrentGameMode.Build : CurrentGameMode.Play; return (CurrentGameMode) GameMode.CurrentMode;
} }
} }

View file

@ -11,9 +11,9 @@ namespace TechbloxModdingAPI.App
{ {
public class GameBuildSimEventEngine : IApiEngine, IUnorderedInitializeOnTimeRunningModeEntered, IUnorderedInitializeOnTimeStoppedModeEntered 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"; public string Name => "TechbloxModdingAPIBuildSimEventGameEngine";
@ -27,13 +27,13 @@ namespace TechbloxModdingAPI.App
public JobHandle OnInitializeTimeRunningMode(JobHandle inputDeps) 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; return inputDeps;
} }
public JobHandle OnInitializeTimeStoppedMode(JobHandle 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; return inputDeps;
} }
} }

View file

@ -1,6 +1,7 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using HarmonyLib; using HarmonyLib;
using RobocraftX; using RobocraftX;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.Schedulers; using RobocraftX.Schedulers;
@ -9,25 +10,18 @@ using Svelto.ECS;
using Svelto.Tasks; using Svelto.Tasks;
using Svelto.Tasks.Lean; using Svelto.Tasks.Lean;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using RobocraftX.Common.Loading;
using RobocraftX.Multiplayer;
using RobocraftX.ScreenshotTaker; using RobocraftX.ScreenshotTaker;
using Techblox.Environment.Transition;
using Techblox.GameSelection;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App 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"; public string Name => "TechbloxModdingAPIGameInfoMenuEngine";
@ -35,35 +29,16 @@ namespace TechbloxModdingAPI.App
public EntitiesDB entitiesDB { set; private get; } public EntitiesDB entitiesDB { set; private get; }
private bool enteredGame;
private bool loadingFinished;
private bool playerJoined;
public void Dispose() public void Dispose()
{ {
if (GameReloadedPatch.IsReload) ExceptionUtil.InvokeEvent(ExitGame, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
return; // Toggling time mode
ExitGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID });
IsInGame = false; IsInGame = false;
loadingFinished = false;
playerJoined = false;
enteredGame = false;
} }
public void Ready() public void Ready()
{ {
if (GameReloadedPatch.IsReload) ExceptionUtil.InvokeEvent(EnterGame, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
return; // Toggling time mode IsInGame = true;
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();
} }
// game functionality // game functionality
@ -78,7 +53,7 @@ namespace TechbloxModdingAPI.App
{ {
if (async) if (async)
{ {
ExitCurrentGameAsync().RunOn(ClientLean.EveryFrameStepRunner_TimeRunningAndStopped); ExitCurrentGameAsync().RunOn(Lean.EveryFrameStepRunner_TimeRunningAndStopped);
} }
else else
{ {
@ -118,48 +93,45 @@ namespace TechbloxModdingAPI.App
public void ToggleTimeMode() public void ToggleTimeMode()
{ {
if (TimeRunningModeUtil.IsTimeStoppedMode(entitiesDB)) if (!entitiesDB.FoundInGroups<BlockTagEntityStruct>())
FakeInput.ActionInput(toggleMode: true); throw new AppStateException("At least one block must exist in the world to enter simulation");
else TimeRunningModeUtil.ToggleTimeRunningState(entitiesDB);
{ }
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); public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid)
} {
} var allBlocks = entitiesDB.QueryEntities<BlockTagEntityStruct>();
List<EGID> blockEGIDs = new List<EGID>();
if (filter == BlockIDs.Invalid)
{
foreach (var (blocks, _) in allBlocks)
{
var buffer = blocks.ToBuffer().buffer;
for (int i = 0; i < buffer.capacity; i++)
blockEGIDs.Add(buffer[i].ID);
}
public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid) return blockEGIDs.ToArray();
{ }
var allBlocks = entitiesDB.QueryEntities<BlockTagEntityStruct>(); else
List<EGID> blockEGIDs = new List<EGID>(); {
foreach (var ((_, ids, count), group) in allBlocks) foreach (var (blocks, _) in allBlocks)
{ {
for (int i = 0; i < count; i++) var array = blocks.ToBuffer().buffer;
{ for (var index = 0; index < array.capacity; index++)
var id = new EGID(ids[i], group); {
uint dbid; var block = array[index];
if (filter == BlockIDs.Invalid) uint dbid = entitiesDB.QueryEntity<DBEntityStruct>(block.ID).DBID;
dbid = (uint)filter; if (dbid == (ulong) filter)
else blockEGIDs.Add(block.ID);
dbid = entitiesDB.QueryEntity<DBEntityStruct>(id).DBID; }
var ownership = entitiesDB.QueryEntity<BlockOwnershipComponent>(id).BlockOwnership; }
if ((ownership & BlockOwnership.User) != 0 && dbid == (ulong)filter)
blockEGIDs.Add(id);
}
}
return blockEGIDs.ToArray(); return blockEGIDs.ToArray();
} }
}
public void EnableScreenshotTaker() public void EnableScreenshotTaker()
{ {
ref var local = ref entitiesDB.QueryEntity<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker); ref var local = ref entitiesDB.QueryEntity<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker);
if (local.enabled) if (local.enabled)
@ -167,29 +139,5 @@ namespace TechbloxModdingAPI.App
local.enabled = true; local.enabled = true;
entitiesDB.PublishEntityChange<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker); 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;
using System.Reflection;
using HarmonyLib; using HarmonyLib;
using RobocraftX; using RobocraftX;
using RobocraftX.Common;
using RobocraftX.GUI; using RobocraftX.GUI;
using RobocraftX.GUI.MyGamesScreen; using RobocraftX.GUI.MyGamesScreen;
using RobocraftX.Multiplayer;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Experimental; using Svelto.ECS.Experimental;
using Techblox.GameSelection; using Svelto.DataStructures;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -17,9 +15,6 @@ namespace TechbloxModdingAPI.App
{ {
public class GameMenuEngine : IFactoryEngine public class GameMenuEngine : IFactoryEngine
{ {
public WrappedHandler<MenuEventArgs> EnterMenu;
public WrappedHandler<MenuEventArgs> ExitMenu;
public IEntityFactory Factory { set; private get; } public IEntityFactory Factory { set; private get; }
public string Name => "TechbloxModdingAPIGameInfoGameEngine"; public string Name => "TechbloxModdingAPIGameInfoGameEngine";
@ -28,42 +23,23 @@ namespace TechbloxModdingAPI.App
public EntitiesDB entitiesDB { set; private get; } 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() public void Dispose()
{ {
IsInMenu = false;
} }
public void Ready() public void Ready()
{ {
MenuEnteredEnginePatch.IsInMenu = true; // At first it uses ActivateMenu(), then GoToMenu() which is patched IsInMenu = true;
MenuEnteredEnginePatch.EnteredExitedMenu();
} }
// game functionality // game functionality
public bool IsInMenu => MenuEnteredEnginePatch.IsInMenu; public bool IsInMenu
public Game[] GetMyGames()
{ {
var (mgsevs, count) = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames); get;
Game[] games = new Game[count]; private set;
for (int i = 0; i < count; i++) } = false;
{
Utility.Logging.MetaDebugLog($"Found game named {mgsevs[i].GameName}");
games[i] = new Game(mgsevs[i].ID);
}
return games;
}
public bool CreateMyGame(EGID id, string path = "", uint thumbnailId = 0, string gameName = "", string creatorName = "", string description = "", long createdDate = 0L) 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() 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; 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; return max;
@ -99,19 +76,15 @@ namespace TechbloxModdingAPI.App
{ {
if (!ExistsGameInfo(id)) return false; if (!ExistsGameInfo(id)) return false;
ref MyGameDataEntityStruct mgdes = ref GetGameInfo(id); 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; GameMode.CurrentMode = autoEnterSim ? RCXMode.Play : RCXMode.Build;
ref var selection = ref entitiesDB.QueryEntity<GameSelectionComponent>(GameSelectionConstants.GameSelectionEGID); GameMode.SaveGameDetails = new SaveGameDetails(gameName, path, workshopId);
selection.userContentID.Set(fileId); // the private FullGameCompositionRoot.SwitchToGame() method gets passed to menu items for this reason
selection.triggerStart = true; AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame").Invoke(FullGameFields.Instance, new object[0]);
selection.saveType = SaveType.ExistingSave;
selection.saveName = gameName;
selection.gameMode = GameMode.PlayGame;
selection.gameID.Set("GAMEID_Road_Track"); //TODO: Expose to the API
return true; return true;
} }
@ -157,41 +130,4 @@ namespace TechbloxModdingAPI.App
} }
internal class MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal : GenericEntityDescriptor<MyGameDataEntityStruct> { } 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,9 @@ using Svelto.ECS.EntityStructs;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using Unity.Mathematics; using Unity.Mathematics;
using Gamecraft.Blocks.GUI;
using HarmonyLib; using HarmonyLib;
using RobocraftX.PilotSeat;
using RobocraftX.Rendering;
using Techblox.BlockLabelsServer;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Blocks.Engines; using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Tests;
@ -67,22 +66,17 @@ namespace TechbloxModdingAPI
/// <returns>The block object or null if doesn't exist</returns> /// <returns>The block object or null if doesn't exist</returns>
public static Block GetLastPlacedBlock() public static Block GetLastPlacedBlock()
{ {
uint lastBlockID = CommonExclusiveGroups.blockIDGeneratorClient.Peek() - 1; uint lastBlockID = (uint) AccessTools.Field(typeof(CommonExclusiveGroups), "_nextBlockEntityID").GetValue(null) - 1;
EGID? egid = BlockEngine.FindBlockEGID(lastBlockID); EGID? egid = BlockEngine.FindBlockEGID(lastBlockID);
return egid.HasValue ? New(egid.Value) : null; return egid.HasValue ? New(egid.Value) : null;
} }
/*public static Block CreateGhostBlock()
{
return BlockGroup._engine.BuildGhostChild();
}*/
/// <summary> /// <summary>
/// An event that fires each time a block is placed. /// An event that fires each time a block is placed.
/// </summary> /// </summary>
public static event EventHandler<BlockPlacedRemovedEventArgs> Placed 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; remove => BlockEventsEngine.Placed -= value;
} }
@ -91,7 +85,7 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public static event EventHandler<BlockPlacedRemovedEventArgs> Removed public static event EventHandler<BlockPlacedRemovedEventArgs> Removed
{ {
add => BlockEventsEngine.Removed += value; add => BlockEventsEngine.Removed += ExceptionUtil.WrapHandler(value);
remove => BlockEventsEngine.Removed -= value; remove => BlockEventsEngine.Removed -= value;
} }
@ -99,42 +93,19 @@ namespace TechbloxModdingAPI
new Dictionary<ExclusiveBuildGroup, (Func<EGID, Block>, Type)> new Dictionary<ExclusiveBuildGroup, (Func<EGID, Block>, Type)>
{ {
{CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, (id => new DampedSpring(id), typeof(DampedSpring))}, {CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, (id => new DampedSpring(id), typeof(DampedSpring))},
{CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))}, {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))}
}; };
static Block() internal static Block New(EGID egid)
{ {
foreach (var group in SeatGroups.SEATS_BLOCK_GROUPS) // Adds driver and passenger seats, occupied and unoccupied return GroupToConstructor.ContainsKey(egid.groupID)
GroupToConstructor.Add(group, (id => new Seat(id), typeof(Seat))); ? GroupToConstructor[egid.groupID].Constructor(egid)
: new Block(egid);
} }
/// <summary> public Block(EGID id)
/// 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)
{ {
Id = id;
Type expectedType; Type expectedType;
if (GroupToConstructor.ContainsKey(id.groupID) && if (GroupToConstructor.ContainsKey(id.groupID) &&
!GetType().IsAssignableFrom(expectedType = GroupToConstructor[id.groupID].Type)) !GetType().IsAssignableFrom(expectedType = GroupToConstructor[id.groupID].Type))
@ -161,18 +132,17 @@ namespace TechbloxModdingAPI
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param> /// <param name="autoWire">Whether the block should be auto-wired (if functional)</param>
/// <param name="player">The player who placed the block</param> /// <param name="player">The player who placed the block</param>
public Block(BlockIDs type, float3 position, bool autoWire = false, Player player = null) 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;
})
{ {
if (!PlacementEngine.IsInGame || !GameState.IsBuildMode())
throw new BlockException("Blocks can only be placed in build mode.");
var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire);
Id = initializer.EGID;
InitData = initializer;
Placed += OnPlacedInit;
} }
public override EGID Id { get; }
private EGID copiedFrom; private EGID copiedFrom;
/// <summary> /// <summary>
@ -288,7 +258,6 @@ namespace TechbloxModdingAPI
color.indexInPalette = value.Index; color.indexInPalette = value.Index;
color.hasNetworkChange = true; color.hasNetworkChange = true;
color.paletteColour = BlockEngine.ConvertBlockColor(color.indexInPalette); //Setting to 255 results in black color.paletteColour = BlockEngine.ConvertBlockColor(color.indexInPalette); //Setting to 255 results in black
BlockEngine.UpdateBlockColor(Id);
} }
} }
@ -325,10 +294,7 @@ namespace TechbloxModdingAPI
: throw new BlockTypeException("Unknown block type! Could not set default material."); : throw new BlockTypeException("Unknown block type! Could not set default material.");
if (!FullGameFields._dataDb.ContainsKey<MaterialPropertiesData>(val)) if (!FullGameFields._dataDb.ContainsKey<MaterialPropertiesData>(val))
throw new BlockException($"Block material {value} does not exist!"); throw new BlockException($"Block material {value} does not exist!");
ref var comp = ref BlockEngine.GetBlockInfo<CubeMaterialStruct>(this); BlockEngine.GetBlockInfo<CubeMaterialStruct>(this).materialId = val;
if (comp.materialId == val)
return;
comp.materialId = val;
BlockEngine.UpdatePrefab(this, val, Flipped); //The default causes the screen to go black BlockEngine.UpdatePrefab(this, val, Flipped); //The default causes the screen to go black
} }
} }
@ -340,15 +306,11 @@ namespace TechbloxModdingAPI
[TestValue(null)] [TestValue(null)]
public string Label public string Label
{ {
get get => BlockEngine.GetBlockInfoViewComponent<TextLabelEntityViewStruct>(this).textLabelComponent?.text;
{
var opt = BlockEngine.GetBlockInfoOptional<LabelResourceIDComponent>(this);
return opt ? FullGameFields._managers.blockLabelResourceManager.GetText(opt.Get().instanceID) : null;
}
set set
{ {
var opt = BlockEngine.GetBlockInfoOptional<LabelResourceIDComponent>(this); var comp = BlockEngine.GetBlockInfoViewComponent<TextLabelEntityViewStruct>(this).textLabelComponent;
if (opt) FullGameFields._managers.blockLabelResourceManager.SetText(opt.Get().instanceID, value); if (comp != null) comp.text = value;
} }
} }
@ -366,12 +328,8 @@ namespace TechbloxModdingAPI
get get
{ {
if (blockGroup != null) return blockGroup; if (blockGroup != null) return blockGroup;
if (!GameState.IsBuildMode()) return null; // Breaks in simulation
var bgec = BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this); var bgec = BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this);
return blockGroup = bgec.currentBlockGroup == -1 return blockGroup = bgec.currentBlockGroup == -1 ? null : new BlockGroup(bgec.currentBlockGroup, this);
? null
: GetInstance(new EGID((uint)bgec.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup),
egid => new BlockGroup((int)egid.entityID, this));
} }
set set
{ {
@ -382,8 +340,6 @@ namespace TechbloxModdingAPI
return; return;
} }
blockGroup?.RemoveInternal(this); blockGroup?.RemoveInternal(this);
if (!InitData.Valid)
return;
BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this).currentBlockGroup = (int?) value?.Id.entityID ?? -1; BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this).currentBlockGroup = (int?) value?.Id.entityID ?? -1;
value?.AddInternal(this); value?.AddInternal(this);
blockGroup = value; blockGroup = value;
@ -395,25 +351,8 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public bool Static public bool Static
{ {
get => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic; get => BlockEngine.GetBlockInfo<OverrideStaticComponent>(this).staticIfUnconnected;
set => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic = value; set => BlockEngine.GetBlockInfo<OverrideStaticComponent>(this).staticIfUnconnected = 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;
} }
/// <summary> /// <summary>
@ -441,10 +380,9 @@ namespace TechbloxModdingAPI
public SimBody GetSimBody() public SimBody GetSimBody()
{ {
var st = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(this); var st = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(this);
/*return st.machineRigidBodyId != uint.MaxValue return st.machineRigidBodyId != uint.MaxValue
? new SimBody(st.machineRigidBodyId, st.clusterId) - TODO: ? new SimBody(st.machineRigidBodyId, st.clusterId)
: null;*/ : null;
return null;
} }
/// <summary> /// <summary>
@ -468,7 +406,7 @@ namespace TechbloxModdingAPI
if (e.ID != Id) return; if (e.ID != Id) return;
Placed -= OnPlacedInit; //And we can reference it 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 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); BlockCloneEngine.CopyBlockStats(copiedFrom, Id);
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
@ -19,16 +19,17 @@ namespace TechbloxModdingAPI
public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable
{ {
internal static BlueprintEngine _engine = new BlueprintEngine(); internal static BlueprintEngine _engine = new BlueprintEngine();
public override EGID Id { get; }
private readonly Block sourceBlock; private readonly Block sourceBlock;
private readonly List<Block> blocks; private readonly List<Block> blocks;
private float3 position, rotation; private float3 position, rotation;
internal bool PosAndRotCalculated; internal bool PosAndRotCalculated;
internal BlockGroup(int id, Block block) : base(new EGID((uint)id, internal BlockGroup(int id, Block block)
BlockGroupExclusiveGroups.BlockGroupEntityGroup))
{ {
if (id == BlockGroupUtility.GROUP_UNASSIGNED) if (id == BlockGroupUtility.GROUP_UNASSIGNED)
throw new BlockException("Cannot create a block group for blocks without a group!"); throw new BlockException("Cannot create a block group for blocks without a group!");
Id = new EGID((uint) id, BlockGroupExclusiveGroups.BlockGroupEntityGroup);
sourceBlock = block; sourceBlock = block;
blocks = new List<Block>(GetBlocks()); blocks = new List<Block>(GetBlocks());
Block.Removed += OnBlockRemoved; 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,32 @@ namespace TechbloxModdingAPI.Blocks
PlateQuarterPyramid, PlateQuarterPyramid,
PlateTetrahedron, PlateTetrahedron,
Sphere, Sphere,
CarWheelArch = 47, Frame,
FrameS1,
FrameS2,
FrameS3,
FrameS4,
FrameS5,
FrameWedge,
FrameWedgeS1,
FrameWedgeS2,
FrameWedgeS3,
FrameWedgeS4,
SideS0S3 = 29,
SideS0S5 = 31,
SideS1S1,
SideS1S2,
SideS1S3,
SideS1S4,
SideS1S5,
SideS2S1,
SideS2S2,
SideS2S3,
SideS2S4,
SideS2S5,
WindscreenS3 = 44,
WindscreenS5 = 46,
CarWheelArch,
CarArchSmallFlare, CarArchSmallFlare,
CarArchFlare, CarArchFlare,
CarArchExtrudedFlare, CarArchExtrudedFlare,
@ -42,8 +67,8 @@ namespace TechbloxModdingAPI.Blocks
PlateTriangle = 130, PlateTriangle = 130,
PlateCircle, PlateCircle,
PlateQuarterCircle, PlateQuarterCircle,
PlateRoundedWedge, PlateRWedge,
PlateRoundedTetrahedron, PlateRTetrahedron,
Cone, Cone,
ConeSegment, ConeSegment,
DoubleSliced, DoubleSliced,
@ -113,271 +138,5 @@ namespace TechbloxModdingAPI.Blocks
CarWheel, CarWheel,
GoKartWheelWideProfile, GoKartWheelWideProfile,
GoKartWheel, 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

@ -15,32 +15,6 @@ namespace TechbloxModdingAPI.Blocks
SteelBodyworkRustedPaint, SteelBodyworkRustedPaint,
SteelBodyworkHeavyRust, SteelBodyworkHeavyRust,
WoodVarnishedDark, WoodVarnishedDark,
Chrome, 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,
} }
} }

View file

@ -7,6 +7,7 @@ using DataLoader;
using Svelto.Tasks; using Svelto.Tasks;
using Unity.Mathematics; using Unity.Mathematics;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -45,20 +46,16 @@ namespace TechbloxModdingAPI.Blocks
"Block ID enum matches the known block types."); "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)] [APITestCase(TestType.EditMode)]
public static void TestBlockIDs() public static void TestBlockIDs()
{ {
float3 pos = new float3(); float3 pos = new float3();
var values = Enum.GetValues(typeof(BlockIDs)); foreach (BlockIDs id in Enum.GetValues(typeof(BlockIDs)))
blocks = new Block[values.Length - 1]; // Minus the invalid ID
int i = 0;
foreach (BlockIDs id in values)
{ {
if (id == BlockIDs.Invalid) continue; if (id == BlockIDs.Invalid) continue;
try try
{ {
blocks[i++] = Block.PlaceNew(id, pos); Block.PlaceNew(id, pos);
pos += 0.2f; pos += 0.2f;
} }
catch (Exception e) catch (Exception e)
@ -74,55 +71,39 @@ namespace TechbloxModdingAPI.Blocks
[APITestCase(TestType.EditMode)] [APITestCase(TestType.EditMode)]
public static IEnumerator<TaskContract> TestBlockProperties() public static IEnumerator<TaskContract> TestBlockProperties()
{ //Uses the result of the previous test case { //Uses the result of the previous test case
var blocks = Game.CurrentGame().GetBlocksInGame();
yield return Yield.It; yield return Yield.It;
if (blocks is null)
yield break;
for (var index = 0; index < blocks.Length; index++) 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]; var block = blocks[index];
if (!block.Exists) continue; if (!block.Exists) continue;
foreach (var property in block.GetType().GetProperties()) foreach (var property in block.GetType().GetProperties())
{ {
//Includes specialised block properties //Includes specialised block properties
if (property.SetMethod == null) continue; if (property.SetMethod == null) continue;
var testValues = new (Type, object, Predicate<object>)[]
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)>)[]
{ {
//(type, default value, predicate or null for equality) //(type, default value, predicate or null for equality)
(typeof(long), 3, null), (typeof(long), 3, null),
(typeof(int), 4, null), (typeof(int), 4, null),
(typeof(double), 5.2f, t => Math.Abs((double) t.Value - (double) t.Default) < float.Epsilon), (typeof(double), 5.2f, obj => Math.Abs((double) obj - 5.2f) < float.Epsilon),
(typeof(float), 5.2f, t => Math.Abs((float) t.Value - (float) t.Default) < float.Epsilon), (typeof(float), 5.2f, obj => Math.Abs((float) obj - 5.2f) < float.Epsilon),
(typeof(bool), true, t => (bool) t.Value), (typeof(bool), true, obj => (bool) obj),
(typeof(string), "Test", t => (string) t.Value == "Test"), //String equality check (typeof(string), "Test", obj => (string) obj == "Test"), //String equality check
(typeof(float3), (float3) 20, t => math.all(Float3Compare((float3)t.Value, (float3)t.Default))), (typeof(float3), (float3) 2, obj => math.all((float3) obj - 2 < (float3) float.Epsilon)),
(typeof(BlockColor), new BlockColor(BlockColors.Aqua, 2), null), (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; var propType = property.PropertyType;
if (!propType.IsValueType) continue; 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) foreach (var (type, value, predicate) in testValues)
{ {
if (type.IsAssignableFrom(propType)) if (type.IsAssignableFrom(propType))
{ {
valueToUse = value; valueToUse = value;
predicateToUse = predicate ?? (t => Equals(t.Value, t.Default)); predicateToUse = predicate ?? (obj => Equals(obj, value));
break; break;
} }
} }
@ -131,7 +112,7 @@ namespace TechbloxModdingAPI.Blocks
{ {
var values = propType.GetEnumValues(); var values = propType.GetEnumValues();
valueToUse = values.GetValue(values.Length / 2); valueToUse = values.GetValue(values.Length / 2);
predicateToUse = t => Equals(t.Value, t.Default); predicateToUse = val => Equals(val, valueToUse);
} }
if (valueToUse == null) if (valueToUse == null)
@ -140,26 +121,10 @@ namespace TechbloxModdingAPI.Blocks
yield break; yield break;
} }
try property.SetValue(block, valueToUse);
{ object got = property.GetValue(block);
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;
}
var attr = property.GetCustomAttribute<TestValueAttribute>(); 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}."); Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}.");
yield break; yield break;

View file

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

View file

@ -1,382 +1,35 @@
using RobocraftX.Common;
using Svelto.ECS;
using Techblox.EngineBlock;
namespace TechbloxModdingAPI.Blocks namespace TechbloxModdingAPI.Blocks
{ {
using RobocraftX.Common;
using Svelto.ECS;
public class Engine : SignalingBlock public class Engine : SignalingBlock
{ {
public Engine(EGID id) : base(id)
/// <summary>
/// Constructs a(n) Engine object representing an existing block.
/// </summary>
public Engine(EGID egid) :
base(egid)
{ {
} }
/// <summary> public Engine(uint id) : base(new EGID(id, CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP))
/// Constructs a(n) Engine object representing an existing block.
/// </summary>
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 public bool On
{ {
get get => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).engineOn;
{ set => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).engineOn = value;
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn = 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;
}
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 public float CurrentTorque
{ {
get get => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).currentTorque;
{ set => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).currentTorque = value;
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentTorque;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentTorque = value;
}
} }
/// <summary> public int CurrentGear
/// Gets or sets the Engine's TotalWheelVelocityAV property. May not be saved.
/// </summary>
public float TotalWheelVelocityAV
{ {
get get => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).currentGear;
{ set => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).currentGear = value;
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.pickedBlockEntityID = sourceID;
pickedBlock.placedBlockEntityID = targetID; pickedBlock.placedBlockEntityID = targetID;
pickedBlock.placedBlockTweaksMustCopy = true; pickedBlock.placedBlockTweaksMustCopy = true;
if (entitiesDB.Exists<BlockTagEntityStruct>(pickedBlock.pickedBlockEntityID) if (entitiesDB.Exists<DBEntityStruct>(pickedBlock.pickedBlockEntityID)
&& entitiesDB.Exists<BlockTagEntityStruct>(pickedBlock.placedBlockEntityID)) && entitiesDB.Exists<DBEntityStruct>(pickedBlock.placedBlockEntityID))
{ {
copyFromBlock.Invoke(Patch.copyEngine, new object[] {pickedBlock.ID, pickedBlock}); 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}); 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}); copyWireToBlock.Invoke(Patch.createWireEngine, new object[] {group, pickedBlock.ID});
pickedBlock.placedBlockTweaksMustCopy = false; pickedBlock.placedBlockTweaksMustCopy = false;

View file

@ -1,9 +1,8 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using HarmonyLib;
using Gamecraft.ColourPalette; using Gamecraft.ColourPalette;
using Gamecraft.TimeRunning;
using Gamecraft.Wires; using Gamecraft.Wires;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using RobocraftX.Common; using RobocraftX.Common;
@ -13,16 +12,11 @@ using RobocraftX.Rendering.GPUI;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using Svelto.ECS.Experimental;
using Svelto.ECS.Hybrid; using Svelto.ECS.Hybrid;
using Techblox.BuildingDrone;
using Techblox.ObjectIDBlockServer;
using Unity.Mathematics; using Unity.Mathematics;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
using PrefabsID = RobocraftX.Common.PrefabsID;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {
@ -47,21 +41,22 @@ namespace TechbloxModdingAPI.Blocks.Engines
public Block[] GetConnectedBlocks(EGID blockID) 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>(); Stack<EGID> cubeStack = new Stack<EGID>();
FasterList<EGID> cubes = new FasterList<EGID>(10); FasterList<EGID> cubes = new FasterList<EGID>(10);
var coll = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); 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,
/*ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubes, (in GridConnectionsEntityStruct g) => { return false; });
(in GridConnectionsEntityStruct _) => false);*/
var ret = new Block[cubes.count]; var ret = new Block[cubes.count];
for (int i = 0; i < cubes.count; i++) for (int i = 0; i < cubes.count; i++)
@ -72,7 +67,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
public float4 ConvertBlockColor(byte index) => index == byte.MaxValue public float4 ConvertBlockColor(byte index) => index == byte.MaxValue
? new float4(-1f, -1f, -1f, -1f) ? new float4(-1f, -1f, -1f, -1f)
: entitiesDB.QueryEntity<PaletteEntryEntityStruct>(index, : 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 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 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); return ref entitiesDB.QueryEntityOrDefault<T>(block);
} }
@ -98,35 +89,13 @@ namespace TechbloxModdingAPI.Blocks.Engines
return ref entitiesDB.QueryEntityOrDefault<T>(block); 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) public void UpdateDisplayedBlock(EGID id)
{ {
if (!BlockExists(id)) return; if (!BlockExists(id)) return;
var pos = entitiesDB.QueryEntity<PositionEntityStruct>(id); var pos = entitiesDB.QueryEntity<PositionEntityStruct>(id);
var rot = entitiesDB.QueryEntity<RotationEntityStruct>(id); var rot = entitiesDB.QueryEntity<RotationEntityStruct>(id);
var scale = entitiesDB.QueryEntity<ScalingEntityStruct>(id); var scale = entitiesDB.QueryEntity<ScalingEntityStruct>(id);
var skew = entitiesDB.QueryEntity<SkewComponent>(id); entitiesDB.QueryEntity<RenderingDataStruct>(id).matrix = float4x4.TRS(pos.position, rot.rotation, scale.scale);
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
} }
internal void UpdatePrefab(Block block, byte material, bool flipped) internal void UpdatePrefab(Block block, byte material, bool flipped)
@ -137,58 +106,47 @@ namespace TechbloxModdingAPI.Blocks.Engines
: uint.MaxValue; : uint.MaxValue;
if (prefabAssetID == 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 throw new BlockException("Prefab asset ID not found for block " + block); //Set by the game
return; return;
} }
uint prefabId = uint prefabId =
PrefabsID.GetOrAddPrefabID((ushort) prefabAssetID, material, 1, flipped); PrefabsID.GetOrCreatePrefabID((ushort) prefabAssetID, material, 1, flipped);
entitiesDB.QueryEntityOrDefault<GFXPrefabEntityStructGPUI>(block).prefabID = prefabId; entitiesDB.QueryEntityOrDefault<GFXPrefabEntityStructGPUI>(block).prefabID = prefabId;
if (block.Exists) if (block.Exists)
{ entitiesDB.PublishEntityChange<GFXPrefabEntityStructGPUI>(block.Id);
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);
}
//Phyiscs prefab: prefabAssetID, set on block creation from the CubeListData //Phyiscs prefab: prefabAssetID, set on block creation from the CubeListData
} }
public void UpdateBlockColor(EGID id)
{
entitiesDB.PublishEntityChangeDelayed<ColourParameterEntityStruct>(id);
}
public bool BlockExists(EGID blockID) public bool BlockExists(EGID blockID)
{ {
return entitiesDB.Exists<BlockTagEntityStruct>(blockID); return entitiesDB.Exists<DBEntityStruct>(blockID);
} }
public SimBody[] GetSimBodiesFromID(byte id) public SimBody[] GetSimBodiesFromID(byte id)
{ {
var ret = new FasterList<SimBody>(4); 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; 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++)
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
{ {
if (rb.Id.entityID == rid) ref ObjectIdEntityStruct oid = ref oids[i];
goto DUPLICATE; //Multiple Object Identifiers on one rigid body 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.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)); ret.Add(new SimBody(rid));
DUPLICATE: ;*/ DUPLICATE: ;
}
} }
return ret.ToArray(); return ret.ToArray();
@ -196,14 +154,14 @@ namespace TechbloxModdingAPI.Blocks.Engines
public SimBody[] GetConnectedSimBodies(uint id) 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); 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.isBroken) continue;
/*if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB)); - TODO: if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB));
else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA));*/ else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA));
} }
return list.ToArray(); return list.ToArray();
@ -213,13 +171,14 @@ namespace TechbloxModdingAPI.Blocks.Engines
{ {
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
var bodies = new HashSet<uint>(); 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]; var conn = array[index];
/*if (conn.clusterId == cid) - TODO if (conn.clusterId == cid)
bodies.Add(conn.machineRigidBodyId);*/ bodies.Add(conn.machineRigidBodyId);
} }
} }
@ -228,10 +187,10 @@ namespace TechbloxModdingAPI.Blocks.Engines
public EGID? FindBlockEGID(uint id) public EGID? FindBlockEGID(uint id)
{ {
var groups = entitiesDB.FindGroups<BlockTagEntityStruct>(); var groups = entitiesDB.FindGroups<DBEntityStruct>();
foreach (ExclusiveGroupStruct group in groups) foreach (ExclusiveGroupStruct group in groups)
{ {
if (entitiesDB.Exists<BlockTagEntityStruct>(id, group)) if (entitiesDB.Exists<DBEntityStruct>(id, group))
return new EGID(id, group); return new EGID(id, group);
} }
@ -241,14 +200,15 @@ namespace TechbloxModdingAPI.Blocks.Engines
public Cluster GetCluster(uint sbid) public Cluster GetCluster(uint sbid)
{ {
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); 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 //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: if (conn.machineRigidBodyId == sbid && conn.clusterId != uint.MaxValue)
return new Cluster(conn.clusterId);*/ return new Cluster(conn.clusterId);
} }
} }
@ -257,36 +217,27 @@ namespace TechbloxModdingAPI.Blocks.Engines
public Block[] GetBodyBlocks(uint sbid) public Block[] GetBodyBlocks(uint sbid)
{ {
var groups = entitiesDB.FindGroups<GridConnectionsEntityStruct>(); var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
groups = new QueryGroups(groups).Except(CommonExclusiveGroups.DISABLED_JOINTS_IN_SIM_GROUP).Evaluate().result;
var set = new HashSet<Block>(); 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]; var conn = array[index];
/*if (conn.machineRigidBodyId == sbid) - TODO if (conn.machineRigidBodyId == sbid)
set.Add(Block.New(tags[index].ID));*/ set.Add(Block.New(conn.ID));
} }
} }
return set.ToArray(); return set.ToArray();
} }
public ObjectID[] GetObjectIDsFromID(byte id) #if DEBUG
public EntitiesDB GetEntitiesDB()
{ {
if (!entitiesDB.HasAny<ObjectIDTweakableComponent>(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP)) return entitiesDB;
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();
} }
#endif
} }
} }

View file

@ -10,8 +10,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
{ {
public class BlockEventsEngine : IReactionaryEngine<BlockTagEntityStruct> public class BlockEventsEngine : IReactionaryEngine<BlockTagEntityStruct>
{ {
public WrappedHandler<BlockPlacedRemovedEventArgs> Placed; public event EventHandler<BlockPlacedRemovedEventArgs> Placed;
public WrappedHandler<BlockPlacedRemovedEventArgs> Removed; public event EventHandler<BlockPlacedRemovedEventArgs> Removed;
public void Ready() public void Ready()
{ {
@ -26,14 +26,21 @@ namespace TechbloxModdingAPI.Blocks.Engines
public string Name { get; } = "TechbloxModdingAPIBlockEventsEngine"; public string Name { get; } = "TechbloxModdingAPIBlockEventsEngine";
public bool isRemovable { get; } = false; public bool isRemovable { get; } = false;
private bool shouldAddRemove;
public void Add(ref BlockTagEntityStruct entityComponent, EGID egid) 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) 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; public EGID ID;
private Block block; private Block block;
public Block Block => block ??= Block.New(ID); public Block Block => block ?? (block = Block.New(ID));
} }
} }

View file

@ -1,27 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using Gamecraft.Blocks.BlockGroups; using Gamecraft.Blocks.BlockGroups;
using Gamecraft.GUI.Blueprints; using Gamecraft.GUI.Blueprints;
using HarmonyLib; using HarmonyLib;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.CR.MachineEditing.BoxSelect; using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.CR.MachineEditing.BoxSelect.ClipboardOperations; using RobocraftX.CR.MachineEditing.BoxSelect.ClipboardOperations;
using RobocraftX.Physics;
using RobocraftX.Rendering;
using RobocraftX.Rendering.GPUI;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.DataStructures; using Svelto.ECS.DataStructures;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using Svelto.ECS.Native; using Svelto.ECS.Native;
using Svelto.ECS.Serialization; using Svelto.ECS.Serialization;
using Techblox.Blocks.Connections;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
using Unity.Collections; using Unity.Collections;
using Unity.Mathematics; using Unity.Mathematics;
using UnityEngine; using UnityEngine;
@ -49,8 +43,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
private static readonly MethodInfo SerializeGhostBlueprint = private static readonly MethodInfo SerializeGhostBlueprint =
AccessTools.Method(SerializeGhostBlueprintType, "SerializeClipboardGhostEntities"); AccessTools.Method(SerializeGhostBlueprintType, "SerializeClipboardGhostEntities");
private static NativeEntityRemove nativeBlockRemove; private static NativeEntityRemove nativeRemove;
private static NativeEntityRemove nativeConnectionRemove;
private static MachineGraphConnectionEntityFactory connectionFactory; private static MachineGraphConnectionEntityFactory connectionFactory;
private static IEntityFunctions entityFunctions; private static IEntityFunctions entityFunctions;
private static ClipboardSerializationDataResourceManager clipboardManager; private static ClipboardSerializationDataResourceManager clipboardManager;
@ -90,8 +83,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
public void RemoveBlockGroup(int id) public void RemoveBlockGroup(int id)
{ {
BlockGroupUtility.RemoveAllBlocksInBlockGroup(id, entitiesDB, removedConnections, nativeBlockRemove, BlockGroupUtility.RemoveAllBlocksInBlockGroup(id, entitiesDB, removedConnections, nativeRemove,
nativeConnectionRemove, connectionFactory, default).Complete(); connectionFactory, default).Complete();
} }
public int CreateBlockGroup(float3 position, quaternion rotation) public int CreateBlockGroup(float3 position, quaternion rotation)
@ -175,8 +168,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
foreach (var block in blocks) foreach (var block in blocks)
{ {
GhostChildUtility.BuildGhostChild(in playerID, block.Id, in pos, in rot, entitiesDB, GhostChildUtility.BuildGhostChild(in playerID, block.Id, in pos, in rot, entitiesDB,
BuildGhostBlueprintFactory, false, bssesopt.Get().buildingDroneReference, BuildGhostBlueprintFactory, false, bssesopt.Get().buildingDroneReference);
FullGameFields._managers.blockLabelResourceManager);
} }
} }
@ -261,77 +253,6 @@ namespace TechbloxModdingAPI.Blocks.Engines
clipboardManager.DecrementRefCount(blueprintID); 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 string Name { get; } = "TechbloxModdingAPIBlueprintGameEngine";
public bool isRemovable { get; } = false; public bool isRemovable { get; } = false;
@ -341,8 +262,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
public static void Prefix(IEntityFunctions entityFunctions, public static void Prefix(IEntityFunctions entityFunctions,
MachineGraphConnectionEntityFactory machineGraphConnectionEntityFactory) MachineGraphConnectionEntityFactory machineGraphConnectionEntityFactory)
{ {
nativeBlockRemove = entityFunctions.ToNativeRemove<BlockEntityDescriptor>("TBAPI" + nameof(BlueprintEngine)); nativeRemove = entityFunctions.ToNativeRemove<BlockEntityDescriptor>("GCAPI" + nameof(BlueprintEngine));
nativeConnectionRemove = entityFunctions.ToNativeRemove<MachineConnectionEntityDescriptor>("TBAPI" + nameof(BlueprintEngine));
connectionFactory = machineGraphConnectionEntityFactory; connectionFactory = machineGraphConnectionEntityFactory;
BlueprintEngine.entityFunctions = entityFunctions; BlueprintEngine.entityFunctions = entityFunctions;
} }

View file

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

View file

@ -16,7 +16,6 @@ using Unity.Mathematics;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {
@ -55,11 +54,11 @@ namespace TechbloxModdingAPI.Blocks.Engines
//RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine //RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine
DBEntityStruct dbEntity = new DBEntityStruct {DBID = block}; 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>() uint prefabAssetID = structInitializer.Has<PrefabAssetIDComponent>()
? structInitializer.Get<PrefabAssetIDComponent>().prefabAssetID ? structInitializer.Get<PrefabAssetIDComponent>().prefabAssetID
: throw new BlockException("Prefab asset ID not found!"); //Set by the game : 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(new GFXPrefabEntityStructGPUI(prefabId));
structInitializer.Init(dbEntity); structInitializer.Init(dbEntity);
structInitializer.Init(new PositionEntityStruct {position = position}); structInitializer.Init(new PositionEntityStruct {position = position});

View file

@ -1,61 +1,46 @@
using System.Reflection; using System.Reflection;
using Gamecraft.Blocks.BlockGroups;
using HarmonyLib; using HarmonyLib;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.GroupTags;
using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Native; using Svelto.ECS.Native;
using Techblox.Blocks.Connections;
using Unity.Collections;
using Unity.Jobs;
using Allocator = Unity.Collections.Allocator;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {
public class RemovalEngine : IApiEngine, IDeterministicTimeStopped public class RemovalEngine : IApiEngine
{ {
private static IEntityFunctions _entityFunctions; private static IEntityFunctions _entityFunctions;
private static MachineGraphConnectionEntityFactory _connectionFactory; private static MachineGraphConnectionEntityFactory _connectionFactory;
private NativeHashSet<ulong> removedConnections;
public bool RemoveBlock(EGID target) public bool RemoveBlock(EGID target)
{ {
if (!entitiesDB.Exists<MachineGraphConnectionsEntityStruct>(target)) if (!entitiesDB.Exists<MachineGraphConnectionsEntityStruct>(target))
return false; return false;
using var connStructMapper = var connections = entitiesDB.QueryEntity<MachineGraphConnectionsEntityStruct>(target);
entitiesDB.QueryNativeMappedEntities<MachineGraphConnectionsEntityStruct>(GroupTag<BLOCK_TAG>.Groups, var groups = entitiesDB.FindGroups<MachineGraphConnectionsEntityStruct>();
Svelto.Common.Allocator.Temp); var connStructMapper =
if (entitiesDB.TryQueryNativeMappedEntities<MachineConnectionComponent>( entitiesDB.QueryNativeMappedEntities<MachineGraphConnectionsEntityStruct>(groups);
ConnectionsExclusiveGroups.MACHINE_CONNECTION_GROUP, out var mapper)) for (int i = connections.connections.Count<MachineConnectionStruct>() - 1; i >= 0; i--)
{ _connectionFactory.RemoveConnection(connections, i, connStructMapper);
BlockGroupUtility.RemoveBlockConnections(target, removedConnections, _connectionFactory,
connStructMapper, mapper, entitiesDB.GetEntityReferenceMap(), _entityFunctions);
}
_entityFunctions.RemoveEntity<BlockEntityDescriptor>(target); _entityFunctions.RemoveEntity<BlockEntityDescriptor>(target);
return true; return true;
} }
public void Ready() public void Ready()
{ {
removedConnections = new(2000, Allocator.Persistent);
} }
public EntitiesDB entitiesDB { get; set; } public EntitiesDB entitiesDB { get; set; }
public void Dispose() public void Dispose()
{ {
removedConnections.Dispose();
} }
public string Name => "TechbloxModdingAPIRemovalGameEngine"; public string Name { get; } = "TechbloxModdingAPIRemovalGameEngine";
public string name => Name;
public bool isRemovable => false; public bool isRemovable => false;
@ -75,12 +60,5 @@ namespace TechbloxModdingAPI.Blocks.Engines
return AccessTools.TypeByName("RobocraftX.CR.MachineEditing.RemoveBlockEngine").GetConstructors()[0]; 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.Common;
using RobocraftX.DOTS; using RobocraftX.UECS;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using Unity.Mathematics; using Unity.Mathematics;
@ -7,7 +7,6 @@ using UnityEngine;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {
@ -41,7 +40,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block); ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block); ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(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 // main (persistent) rotation
Quaternion newRotation = rotStruct.rotation; Quaternion newRotation = rotStruct.rotation;
newRotation.eulerAngles = vector; newRotation.eulerAngles = vector;
@ -51,18 +50,16 @@ namespace TechbloxModdingAPI.Blocks.Engines
// rendered rotation // rendered rotation
transStruct.rotation = newRotation; transStruct.rotation = newRotation;
// collision rotation // collision rotation
if (phyStruct) if (phyStruct.ID != EGID.Empty)
{ //It exists { //It exists
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.Get().dotsEntity, FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity,
new Unity.Transforms.Rotation new Unity.Transforms.Rotation
{ {
Value = rotStruct.rotation Value = rotStruct.rotation
}); });
} }
// TODO: Connections probably need to be assigned (maybe) entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).isProcessed = false;
// They are assigned during machine processing anyway
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).areConnectionsAssigned = false;
return ((Quaternion)rotStruct.rotation).eulerAngles; return ((Quaternion)rotStruct.rotation).eulerAngles;
} }

View file

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

View file

@ -6,7 +6,6 @@ using Svelto.ECS;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {
@ -42,18 +41,19 @@ namespace TechbloxModdingAPI.Blocks.Engines
// implementations for block wiring // 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); EntityInitializer wireInitializer = Factory.BuildEntity<WireEntityDescriptor>(wireEGID);
wireInitializer.Init(new WireEntityStruct wireInitializer.Init(new WireEntityStruct
{ {
sourceBlockEGID = startBlock, sourceBlockEGID = startBlock,
sourcePortUsage = startPort, sourcePortUsage = startPort,
destinationBlockEGID = endBlock, destinationBlockEGID = endBlock,
destinationPortUsage = endPort destinationPortUsage = endPort,
ID = wireEGID
}); });
return (wireInitializer.Get<WireEntityStruct>(), wireEGID); return wireInitializer.Get<WireEntityStruct>();
} }
public ref WireEntityStruct GetWire(EGID wire) public ref WireEntityStruct GetWire(EGID wire)
@ -77,8 +77,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
public ref PortEntityStruct GetPortByOffset(BlockPortsStruct bps, byte portNumber, bool input) public ref PortEntityStruct GetPortByOffset(BlockPortsStruct bps, byte portNumber, bool input)
{ {
ExclusiveGroup group = input ExclusiveGroup group = input
? NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group ? NamedExclusiveGroup<InputPortsGroup>.Group
: NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group; : NamedExclusiveGroup<OutputPortsGroup>.Group;
uint id = (input ? bps.firstInputID : bps.firstOutputID) + portNumber; uint id = (input ? bps.firstInputID : bps.firstOutputID) + portNumber;
EGID egid = new EGID(id, group); EGID egid = new EGID(id, group);
if (!entitiesDB.Exists<PortEntityStruct>(egid)) if (!entitiesDB.Exists<PortEntityStruct>(egid))
@ -116,8 +116,9 @@ namespace TechbloxModdingAPI.Blocks.Engines
public bool SetSignal(uint signalID, float signal, bool input = true) public bool SetSignal(uint signalID, float signal, bool input = true)
{ {
var (array, count) = GetSignalStruct(signalID, out uint index, input); var array = GetSignalStruct(signalID, out uint index, input);
if (count > 0) array[index].valueAsFloat = signal; var arrayB = array.ToBuffer();
if (array.count > 0) arrayB.buffer[index].valueAsFloat = signal;
return false; return false;
} }
@ -129,10 +130,11 @@ namespace TechbloxModdingAPI.Blocks.Engines
public float AddSignal(uint signalID, float signal, bool clamp = true, bool input = true) public float AddSignal(uint signalID, float signal, bool clamp = true, bool input = true)
{ {
var (array, count) = GetSignalStruct(signalID, out uint index, input); var array = GetSignalStruct(signalID, out uint index, input);
if (count > 0) var arrayB = array.ToBuffer();
if (array.count > 0)
{ {
ref var channelData = ref array[index]; ref var channelData = ref arrayB.buffer[index];
channelData.valueAsFloat += signal; channelData.valueAsFloat += signal;
if (clamp) if (clamp)
{ {
@ -160,8 +162,9 @@ namespace TechbloxModdingAPI.Blocks.Engines
public float GetSignal(uint signalID, bool input = true) public float GetSignal(uint signalID, bool input = true)
{ {
var (array, count) = GetSignalStruct(signalID, out uint index, input); var array = GetSignalStruct(signalID, out uint index, input);
return count > 0 ? array[index].valueAsFloat : 0f; var arrayB = array.ToBuffer();
return array.count > 0 ? arrayB.buffer[index].valueAsFloat : 0f;
} }
public uint[] GetSignalIDs(EGID blockID, bool input = true) public uint[] GetSignalIDs(EGID blockID, bool input = true)
@ -190,7 +193,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
EGID[] inputs = new EGID[ports.inputCount]; EGID[] inputs = new EGID[ports.inputCount];
for (uint i = 0; i < ports.inputCount; i++) 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; return inputs;
} }
@ -201,55 +204,70 @@ namespace TechbloxModdingAPI.Blocks.Engines
EGID[] outputs = new EGID[ports.outputCount]; EGID[] outputs = new EGID[ports.outputCount];
for (uint i = 0; i < ports.outputCount; i++) 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; 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)) if (!entitiesDB.Exists<BlockPortsStruct>(block))
return default;
var group = output
? NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group
: NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group;
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; exists = false;
if (!mapper.TryGetArrayAndEntityIndex(entityID, out var index, out var array) || return default;
array[index].usage != portUsage) continue;
return new OptionalRef<PortEntityStruct>(array, index, new EGID(entityID, group));
} }
exists = true;
return default; BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group);
} }
public OptionalRef<WireEntityStruct> MatchPortToWire(PortEntityStruct port, EGID blockID, out EGID wireID) public EGID MatchBlockOutputToPort(Block block, byte portUsage, out bool exists)
{ {
var (wires, ids, count) = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group); var ports = entitiesDB.QueryEntityOptional<BlockPortsStruct>(block);
for (uint i = 0; i < count; i++) 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))
{ {
if ((wires[i].destinationPortUsage == port.usage && wires[i].destinationBlockEGID == blockID) exists = false;
|| (wires[i].sourcePortUsage == port.usage && wires[i].sourceBlockEGID == blockID)) return default;
}
exists = true;
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group);
}
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))
{ {
wireID = new EGID(ids[i], BuildModeWiresGroups.WiresGroup.Group); exists = true;
return new OptionalRef<WireEntityStruct>(wires, i); return ref wiresB[i];
} }
} }
exists = false;
wireID = default; WireEntityStruct[] defRef = new WireEntityStruct[1];
return default; 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; EGID[] startPorts;
if (startPort == byte.MaxValue) if (startPort == byte.MaxValue)
@ -260,7 +278,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
else else
{ {
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(startBlock); 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; EGID[] endPorts;
@ -272,49 +290,59 @@ namespace TechbloxModdingAPI.Blocks.Engines
else else
{ {
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(endBlock); 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++) for (int endIndex = 0; endIndex < endPorts.Length; endIndex++)
{ {
PortEntityStruct endPES = entitiesDB.QueryEntity<PortEntityStruct>(endPorts[endIndex]); PortEntityStruct endPES = entitiesDB.QueryEntity<PortEntityStruct>(endPorts[endIndex]);
for (int startIndex = 0; startIndex < startPorts.Length; startIndex++) for (int startIndex = 0; startIndex < startPorts.Length; startIndex++)
{ {
PortEntityStruct startPES = entitiesDB.QueryEntity<PortEntityStruct>(startPorts[startIndex]); PortEntityStruct startPES = entitiesDB.QueryEntity<PortEntityStruct>(startPorts[startIndex]);
foreach (var wireOpt in entitiesDB.QueryEntitiesOptional<WireEntityStruct>( for (int w = 0; w < wires.count; w++)
NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group))
{ {
var wire = wireOpt.Get(); if ((wiresB[w].destinationPortUsage == endPES.usage && wiresB[w].destinationBlockEGID == endBlock)
if ((wire.destinationPortUsage == endPES.usage && wire.destinationBlockEGID == endBlock) && (wiresB[w].sourcePortUsage == startPES.usage && wiresB[w].sourceBlockEGID == startBlock))
&& (wire.sourcePortUsage == startPES.usage && wire.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); ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID);
var (channels, count) = entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<BuildModeWiresGroups.ChannelDataGroup>.Group); var channels = entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<ChannelDataGroup>.Group);
return port.firstChannelIndexCachedInSim < count var channelsB = channels.ToBuffer();
? new OptionalRef<ChannelDataStruct>(channels, port.firstChannelIndexCachedInSim) if (port.firstChannelIndexCachedInSim < channels.count)
: default; {
exists = true;
return ref channelsB.buffer[port.firstChannelIndexCachedInSim];
}
exists = false;
ChannelDataStruct[] defRef = new ChannelDataStruct[1];
return ref defRef[0];
} }
public EGID[] GetElectricBlocks() public EGID[] GetElectricBlocks()
{ {
var res = new FasterList<EGID>(); 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]; ref BlockPortsStruct s = ref collB.buffer[i];
//res.Add(s.ID); - TODO: Would need to search for the groups for each block res.Add(s.ID);
} }
} }
@ -323,30 +351,55 @@ namespace TechbloxModdingAPI.Blocks.Engines
public EGID[] WiredToInput(EGID block, byte port) public EGID[] WiredToInput(EGID block, byte port)
{ {
return entitiesDB WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup<WiresGroup>.Group,
.QueryEntitiesOptional<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group) (WireEntityStruct wes) => wes.destinationPortUsage == port && wes.destinationBlockEGID == block);
.ToArray(wire => wire.ID, EGID[] result = new EGID[wireEntityStructs.Length];
wire => wire.Component.destinationPortUsage == port && wire.Component.destinationBlockEGID == block); for (uint i = 0; i < wireEntityStructs.Length; i++)
{
result[i] = wireEntityStructs[i].ID;
}
return result;
} }
public EGID[] WiredToOutput(EGID block, byte port) public EGID[] WiredToOutput(EGID block, byte port)
{ {
return entitiesDB WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup<WiresGroup>.Group,
.QueryEntitiesOptional<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group) (WireEntityStruct wes) => wes.sourcePortUsage == port && wes.sourceBlockEGID == block);
.ToArray(wire => wire.ID, EGID[] result = new EGID[wireEntityStructs.Length];
wire => wire.Component.sourcePortUsage == port && wire.Component.sourceBlockEGID == block); 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) private EntityCollection<ChannelDataStruct> GetSignalStruct(uint signalID, out uint index, bool input = true)
{ {
ExclusiveGroup group = input ExclusiveGroup group = input
? NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group ? NamedExclusiveGroup<InputPortsGroup>.Group
: NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group; : NamedExclusiveGroup<OutputPortsGroup>.Group;
if (entitiesDB.Exists<PortEntityStruct>(signalID, group)) if (entitiesDB.Exists<PortEntityStruct>(signalID, group))
{ {
index = entitiesDB.QueryEntity<PortEntityStruct>(signalID, group).firstChannelIndexCachedInSim; index = entitiesDB.QueryEntity<PortEntityStruct>(signalID, group).anyChannelIndex;
var channelData = var channelData =
entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<BuildModeWiresGroups.ChannelDataGroup>.Group); entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<ChannelDataGroup>.Group);
return channelData; 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> /// <returns>The connected wire.</returns>
/// <param name="portId">Port identifier.</param> /// <param name="portId">Port identifier.</param>
/// <param name="connected">Whether the port has a wire connected to it.</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> /// <summary>
@ -56,9 +56,10 @@ namespace TechbloxModdingAPI.Blocks
/// </summary> /// </summary>
/// <returns>The channel data.</returns> /// <returns>The channel data.</returns>
/// <param name="portId">Port identifier.</param> /// <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> /// <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 Svelto.ECS.Experimental;
using TechbloxModdingAPI.Blocks.Engines; using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks namespace TechbloxModdingAPI.Blocks
{ {
public class Wire : EcsObjectBase public class Wire
{ {
internal static SignalEngine signalEngine; internal static SignalEngine signalEngine;
@ -31,8 +30,8 @@ namespace TechbloxModdingAPI.Blocks
public static Wire Connect(SignalingBlock start, byte startPort, SignalingBlock end, byte endPort) public static Wire Connect(SignalingBlock start, byte startPort, SignalingBlock end, byte endPort)
{ {
var (wire, id) = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort); WireEntityStruct wire = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort);
return new Wire(wire, start, end, id); return new Wire(wire, start, end);
} }
/// <summary> /// <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> /// <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) public static Wire ConnectedToInputPort(SignalingBlock end, byte endPort)
{ {
var port = signalEngine.MatchBlockIOToPort(end, endPort, false); EGID port = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists);
if (!port) return null; if (!exists) return null;
var wire = signalEngine.MatchPortToWire(port, end.Id, out var egid); WireEntityStruct wire = signalEngine.MatchPortToWire(port, end.Id, out exists);
return wire if (exists)
? new Wire(wire.Get().sourceBlockEGID, end.Id, wire.Get().sourcePortUsage, endPort, egid, false) {
: null; return new Wire(Block.New(wire.sourceBlockEGID), end, wire.sourcePortUsage, endPort);
}
return null;
} }
/// <summary> /// <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> /// <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) public static Wire ConnectedToOutputPort(SignalingBlock start, byte startPort)
{ {
var port = signalEngine.MatchBlockIOToPort(start, startPort, true); EGID port = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists);
if (!port) return null; if (!exists) return null;
var wire = signalEngine.MatchPortToWire(port, start.Id, out var egid); WireEntityStruct wire = signalEngine.MatchPortToWire(port, start.Id, out exists);
return wire if (exists)
? new Wire(start.Id, wire.Get().destinationBlockEGID, startPort, wire.Get().destinationPortUsage, egid, false) {
: null; return new Wire(start, Block.New(wire.destinationBlockEGID), startPort, wire.destinationPortUsage);
}
return null;
} }
/// <summary> /// <summary>
/// Construct a wire object froam n existing connection. /// Construct a wire object from an existing connection.
/// </summary> /// </summary>
/// <param name="start">Starting block ID.</param> /// <param name="start">Starting block ID.</param>
/// <param name="end">Ending block ID.</param> /// <param name="end">Ending block ID.</param>
/// <param name="startPort">Starting port number, or guess if omitted.</param> /// <param name="startPort">Starting port number, or guess if omitted.</param>
/// <param name="endPort">Ending 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> /// <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; startBlockEGID = start.Id;
th.startBlockEGID = start.Id; endBlockEGID = end.Id;
th.endBlockEGID = end.Id;
bool flipped = false;
// find block ports // find block ports
EGID wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, startPort, endPort); WireEntityStruct wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, out bool exists, startPort, endPort);
if (wire == default) if (exists)
{ {
// flip I/O around and try again wireEGID = wire.ID;
wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, endPort, startPort); endPortEGID = signalEngine.MatchBlockInputToPort(end, wire.destinationPortUsage, out exists);
flipped = true; if (!exists) throw new WireInvalidException("Wire end port not found");
// NB: start and end are handled exactly as they're received as params. startPortEGID = signalEngine.MatchBlockOutputToPort(start, wire.sourcePortUsage, out exists);
// This makes wire traversal easier, but makes logic in this class a bit more complex if (!exists) throw new WireInvalidException("Wire start port not found");
} inputToOutput = false;
endPort = wire.destinationPortUsage;
if (wire != default) startPort = wire.sourcePortUsage;
{
th.Construct(start.Id, end.Id, startPort, endPort, wire, flipped);
} }
else else
{ {
throw new WireInvalidException("Wire not found"); // flip I/O around and try again
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
endPort = wire.sourcePortUsage;
startPort = wire.destinationPortUsage;
}
else
{
throw new WireInvalidException("Wire not found");
}
} }
return th.wireEGID;
})
{
} }
/// <summary> /// <summary>
@ -118,25 +131,25 @@ namespace TechbloxModdingAPI.Blocks
/// <param name="wire">The wire ID.</param> /// <param name="wire">The wire ID.</param>
/// <param name="inputToOutput">Whether the wire direction goes input -> output (true) or output -> input (false, preferred).</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) public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput)
: this(start.Id, end.Id, startPort, endPort, wire, inputToOutput)
{ {
} this.startBlockEGID = start.Id;
this.endBlockEGID = end.Id;
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.inputToOutput = inputToOutput; this.inputToOutput = inputToOutput;
this.wireEGID = wire; this.wireEGID = wire;
endPortEGID = signalEngine.MatchBlockIOToPort(startBlock, startPort, inputToOutput).EGID; if (inputToOutput)
if (endPortEGID == default) throw new WireInvalidException("Wire end port not found"); {
startPortEGID = signalEngine.MatchBlockIOToPort(endBlock, endPort, !inputToOutput).EGID; endPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists);
if (startPortEGID == default) throw new WireInvalidException("Wire start port not found"); 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.startPort = startPort;
this.endPort = endPort; this.endPort = endPort;
} }
@ -145,16 +158,41 @@ namespace TechbloxModdingAPI.Blocks
/// Construct a wire object from an existing wire connection. /// Construct a wire object from an existing wire connection.
/// </summary> /// </summary>
/// <param name="wireEgid">The wire ID.</param> /// <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); WireEntityStruct wire = signalEngine.GetWire(wireEGID);
Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage, this.startBlockEGID = wire.sourceBlockEGID;
wireEgid, false); 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) internal Wire(WireEntityStruct wire, SignalingBlock src, SignalingBlock dest)
: this(src, dest, wire.sourcePortUsage, wire.destinationPortUsage, wireEgid, false)
{ {
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> /// <summary>
@ -164,12 +202,16 @@ namespace TechbloxModdingAPI.Blocks
{ {
get 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 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 get
{ {
return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString; ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
if (!exists) return "";
return cds.valueAsEcsString;
} }
set 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 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 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 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 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> /// </summary>
public SignalingBlock Start public SignalingBlock Start
{ {
get => (SignalingBlock)Block.New(startBlockEGID); get => new SignalingBlock(startBlockEGID);
} }
/// <summary> /// <summary>
@ -238,20 +292,12 @@ namespace TechbloxModdingAPI.Blocks
get => startPort; get => startPort;
} }
/// <summary>
/// The display name of the start port.
/// </summary>
public string StartPortName
{
get => signalEngine.GetPort(startPortEGID).portNameLocalised;
}
/// <summary> /// <summary>
/// The block at the end of the wire. /// The block at the end of the wire.
/// </summary> /// </summary>
public SignalingBlock End public SignalingBlock End
{ {
get => (SignalingBlock)Block.New(endBlockEGID); get => new SignalingBlock(endBlockEGID);
} }
/// <summary> /// <summary>
@ -262,14 +308,6 @@ namespace TechbloxModdingAPI.Blocks
get => endPort; get => endPort;
} }
/// <summary>
/// The display name of the end port.
/// </summary>
public string EndPortName
{
get => signalEngine.GetPort(endPortEGID).portNameLocalised;
}
/// <summary> /// <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. /// 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). /// 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> /// <returns>A copy of the wire object.</returns>
public Wire OutputToInputCopy() public Wire OutputToInputCopy()
{ {
return GetInstance(wireEGID, egid => new Wire(egid)); return new Wire(wireEGID);
} }
/// <summary> /// <summary>
@ -291,11 +329,15 @@ namespace TechbloxModdingAPI.Blocks
{ {
inputToOutput = false; inputToOutput = false;
// swap inputs and outputs // swap inputs and outputs
(endBlockEGID, startBlockEGID) = (startBlockEGID, endBlockEGID); EGID temp = endBlockEGID;
var tempPort = endPortEGID; endBlockEGID = startBlockEGID;
startBlockEGID = temp;
temp = endPortEGID;
endPortEGID = startPortEGID; endPortEGID = startPortEGID;
startPortEGID = tempPort; startPortEGID = temp;
(endPort, startPort) = (startPort, endPort); byte tempPortNumber = endPort;
endPort = startPort;
startPort = tempPortNumber;
} }
} }
@ -303,7 +345,7 @@ namespace TechbloxModdingAPI.Blocks
{ {
if (signalEngine.Exists<WireEntityStruct>(wireEGID)) 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})"; 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 Unity.Mathematics;
using UnityEngine; using UnityEngine;

View file

@ -1,5 +1,6 @@
using Svelto.ECS; using Gamecraft.Damage;
using Techblox.TimeRunning.Clusters; using RobocraftX.Common;
using Svelto.ECS;
namespace TechbloxModdingAPI namespace TechbloxModdingAPI
{ {
@ -9,37 +10,35 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public class Cluster : EcsObjectBase 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
{ {
} get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth;
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth = value;
public float InitialHealth //TODO
{
get => 0f;
set { }
} }
public float CurrentHealth public float CurrentHealth
{ {
get => 0f; get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth;
set { } set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth = value;
} }
public float HealthMultiplier public float HealthMultiplier
{ {
get => 0f; get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier;
set { } 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> /// <summary>
/// Returns the simulation-time rigid bodies for the chunks in this cluster. /// Returns the simulation-time rigid bodies for the chunks in this cluster.
/// </summary> /// </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.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using uREPL;
using RobocraftX.CommandLine.Custom;
namespace TechbloxModdingAPI.Commands namespace TechbloxModdingAPI.Commands
{ {
/// <summary> /// <summary>
@ -14,7 +17,9 @@ namespace TechbloxModdingAPI.Commands
{ {
public static void Register(string name, Action action, string desc, bool noConsole = false) 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) 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) 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) 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) 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) 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) public static void Call(string name)
{ {
CustomCommands.Call(name); RuntimeCommands.Call(name);
} }
public static void Call<Param0>(string name, Param0 param0) 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) 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) 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 System.Linq;
using uREPL;
namespace TechbloxModdingAPI.Commands namespace TechbloxModdingAPI.Commands
{ {
public static class ExistingCommands public static class ExistingCommands
{ {
public static void Call(string commandName) public static void Call(string commandName)
{ {
CustomCommands.Call(commandName); RuntimeCommands.Call(commandName);
} }
public static void Call<Arg0>(string commandName, Arg0 arg0) 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) 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) 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) public static bool Exists(string commandName)
{ {
return CustomCommands.Exists(commandName); return RuntimeCommands.HasRegistered(commandName);
} }
public static (string Name, string Description)[] GetCommandNamesAndDescriptions() 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;
using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Internal; using Svelto.ECS.Internal;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Blocks;
namespace TechbloxModdingAPI namespace TechbloxModdingAPI
{ {
public abstract class EcsObjectBase public abstract class EcsObjectBase
{ {
public EGID Id { get; } public abstract EGID Id { get; } //Abstract to support the 'place' Block constructor
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
protected internal EcsInitData InitData; protected internal EcsInitData InitData;
@ -85,7 +22,7 @@ namespace TechbloxModdingAPI
private EntityReference reference; private EntityReference reference;
public static implicit operator EcsInitData(EntityInitializer initializer) => new EcsInitData public static implicit operator EcsInitData(EntityInitializer initializer) => new EcsInitData
{ group = GetInitGroup(initializer), reference = initializer.reference }; {group = GetInitGroup(initializer), reference = initializer.reference};
public EntityInitializer Initializer(EGID id) => new EntityInitializer(id, group, reference); public EntityInitializer Initializer(EGID id) => new EntityInitializer(id, group, reference);
public bool Valid => group != null; public bool Valid => group != null;
@ -119,10 +56,8 @@ namespace TechbloxModdingAPI
returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType); returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType);
var lambda = var lambda =
Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] { objParam }); Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] {objParam});
return lambda.Compile(); return lambda.Compile();
} }
#endregion
} }
} }

View file

@ -5,68 +5,25 @@ using RobocraftX.CR.MainGame;
using RobocraftX.FrontEnd; using RobocraftX.FrontEnd;
using RobocraftX.StateSync; using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Schedulers;
using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Engines namespace TechbloxModdingAPI.Engines
{ {
[HarmonyPatch] [HarmonyPatch]
static class GameLoadedTimeStoppedEnginePatch class GameLoadedEnginePatch
{ {
public static void Postfix(StateSyncRegistrationHelper stateSyncReg) public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{ {
// register all game engines, including deterministic // register all game engines, including deterministic
GameEngineManager.RegisterEngines(stateSyncReg); 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() 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] [HarmonyPatch]
class MenuLoadedEnginePatch 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> /// <summary>
/// Engine interface to handle ModEventEntityStruct events emitted by IEventEmitterEngines. /// Engine interface to handle ModEventEntityStruct events emitted by IEventEmitterEngines.
/// </summary> /// </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,13 +1,15 @@
using RobocraftX.Common.Input; using System;
using TechbloxModdingAPI.App; using RobocraftX.Common;
using RobocraftX.Common.Input;
using Svelto.ECS;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Input namespace TechbloxModdingAPI.Input
{ {
public static class FakeInput public static class FakeInput
{ {
internal static readonly FakeInputEngine inputEngine = new FakeInputEngine(); private static readonly FakeInputEngine inputEngine = new FakeInputEngine();
/// <summary> /// <summary>
/// Customize the local input. /// Customize the local input.
@ -101,39 +103,39 @@ namespace TechbloxModdingAPI.Input
} }
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) 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}"); //Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}");
// set inputs // set inputs
if (toggleMode) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningModePlay; //TODO: Test, play if (toggleMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningMode;
if (forward) currentInput |= RobocraftX.Common.Input.ActionInput.Forward; if (forward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Forward;
if (backward) currentInput |= RobocraftX.Common.Input.ActionInput.Backward; if (backward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Backward;
if (up) currentInput |= RobocraftX.Common.Input.ActionInput.Up; if (up) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Up;
if (down) currentInput |= RobocraftX.Common.Input.ActionInput.Down; if (down) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Down;
if (left) currentInput |= RobocraftX.Common.Input.ActionInput.Left; if (left) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Left;
if (right) currentInput |= RobocraftX.Common.Input.ActionInput.Right; if (right) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Right;
if (sprint) currentInput |= RobocraftX.Common.Input.ActionInput.Sprint; if (sprint) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Sprint;
//if (toggleFly) currentInput |= RobocraftX.Common.Input.ActionInput.SwitchFlyMode; //if (toggleFly) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SwitchFlyMode;
//if (alt) currentInput |= RobocraftX.Common.Input.ActionInput.AltAction; //if (alt) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.AltAction;
if (primary) currentInput |= RobocraftX.Common.Input.ActionInput.PrimaryActionClick; if (primary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.PrimaryAction;
if (secondary) currentInput |= RobocraftX.Common.Input.ActionInput.SecondaryActionClick; if (secondary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SecondaryAction;
if (tertiary) currentInput |= RobocraftX.Common.Input.ActionInput.TertiaryAction; if (tertiary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.TertiaryAction;
if (primaryHeld) currentInput |= RobocraftX.Common.Input.ActionInput.PrimaryActionHeld; if (primaryHeld) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.PrimaryActionHeld;
if (secondaryHeld) currentInput |= RobocraftX.Common.Input.ActionInput.SecondaryActionHeld; if (secondaryHeld) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SecondaryActionHeld;
//if (toggleUnitGrid) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleUnitGrid; //if (toggleUnitGrid) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleUnitGrid;
if (ctrl) currentInput |= RobocraftX.Common.Input.ActionInput.CtrlAction; if (ctrl) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CtrlAction;
if (toggleColourMode) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleColourMode; if (toggleColourMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleColourMode;
if (scaleBlockUp) currentInput |= RobocraftX.Common.Input.ActionInput.ScaleBlockUp; if (scaleBlockUp) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ScaleBlockUp;
if (scaleBlockDown) currentInput |= RobocraftX.Common.Input.ActionInput.ScaleBlockDown; if (scaleBlockDown) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ScaleBlockDown;
if (rotateBlockClockwise) currentInput |= RobocraftX.Common.Input.ActionInput.RotateBlockClockwise; if (rotateBlockClockwise) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.RotateBlockClockwise;
if (rotateBlockCounterclockwise) currentInput |= RobocraftX.Common.Input.ActionInput.RotateBlockAnticlockwise; if (rotateBlockCounterclockwise) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.RotateBlockAnticlockwise;
if (cutSelection) currentInput |= RobocraftX.Common.Input.ActionInput.CutSelection; if (cutSelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CutSelection;
if (copySelection) currentInput |= RobocraftX.Common.Input.ActionInput.CopySelection; if (copySelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CopySelection;
if (deleteSelection) currentInput |= RobocraftX.Common.Input.ActionInput.DeleteSelection; if (deleteSelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.DeleteSelection;
if(Game.CurrentGame().IsTimeStopped)
inputEngine.HandleCustomInput(); // Only gets called when online, so calling it here as well
} }
public static void Init() public static void Init()

View file

@ -20,8 +20,6 @@ namespace TechbloxModdingAPI.Input
public bool IsReady = false; public bool IsReady = false;
internal ActionInput _localInputCache;
public void Dispose() public void Dispose()
{ {
IsReady = false; IsReady = false;
@ -88,14 +86,6 @@ namespace TechbloxModdingAPI.Input
return ref entitiesDB.QueryEntity<LocalPlayerInputEntityStruct>(egid); return ref entitiesDB.QueryEntity<LocalPlayerInputEntityStruct>(egid);
} }
internal void HandleCustomInput()
{
if (!LocalPlayerIDUtility.DoesLocalPlayerExist(entitiesDB))
return;
GetPlayerInputRef(GetLocalPlayerID()).actionMask |= _localInputCache;
_localInputCache = default;
}
public uint GetLocalPlayerID() public uint GetLocalPlayerID()
{ {
return LocalPlayerIDUtility.GetLocalPlayerID(entitiesDB); 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> /// </summary>
public event EventHandler<bool> OnClick; public event EventHandler<bool> OnClick;
public override void OnGUI() public void OnGUI()
{ {
if (automaticLayout) 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> /// <summary>
/// Initialize a new button with automatic layout. /// Initialize a new button with automatic layout.
/// </summary> /// </summary>
/// <param name="text">The text to display on the button.</param> /// <param name="text">The text to display on the button.</param>
/// <param name="name">The button's name.</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; automaticLayout = true;
this.text = text; this.text = text;
if (name == null)
{
this.Name = typeof(Button).FullName + "::" + text;
}
else
{
this.Name = name;
}
IMGUIManager.AddElement(this);
} }
/// <summary> /// <summary>
@ -64,5 +83,10 @@ namespace TechbloxModdingAPI.Interface.IMGUI
automaticLayout = false; automaticLayout = false;
this.Box = box; this.Box = box;
} }
~Button()
{
IMGUIManager.RemoveElement(this);
}
} }
} }

View file

@ -20,7 +20,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary> /// </summary>
public Rect Box { get; set; } public Rect Box { get; set; }
public override void OnGUI() public void OnGUI()
{ {
/*if (Constants.Default == null) return; /*if (Constants.Default == null) return;
if (Constants.Default.box == null) return;*/ if (Constants.Default.box == null) return;*/
@ -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> /// <summary>
/// The amount of elements in the group. /// The amount of elements in the group.
/// </summary> /// </summary>
@ -75,10 +85,19 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// <param name="box">The rectangular area to use in the window.</param> /// <param name="box">The rectangular area to use in the window.</param>
/// <param name="name">Name of the group.</param> /// <param name="name">Name of the group.</param>
/// <param name="automaticLayout">Whether to use automatic UI layout.</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; Box = box;
if (name == null)
{
this.Name = typeof(Group).FullName + "::" + box.ToString().Replace(" ", "");
}
else
{
this.Name = name;
}
this.automaticLayout = automaticLayout; this.automaticLayout = automaticLayout;
IMGUIManager.AddElement(this);
} }
/// <summary> /// <summary>

View file

@ -1,8 +1,13 @@
using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Utility;
using Rewired.Internal;
using Svelto.DataStructures;
using Svelto.Tasks; using Svelto.Tasks;
using Svelto.Tasks.ExtraLean; using Svelto.Tasks.ExtraLean;
using TechbloxModdingAPI.Tasks; using Svelto.Tasks.ExtraLean.Unity;
using TechbloxModdingAPI.Utility;
using UnityEngine; using UnityEngine;
namespace TechbloxModdingAPI.Interface.IMGUI namespace TechbloxModdingAPI.Interface.IMGUI
@ -15,9 +20,9 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary> /// </summary>
public static class IMGUIManager 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> /// <summary>
/// Add an UIElement instance to be managed by IMGUIManager. /// Add an UIElement instance to be managed by IMGUIManager.

View file

@ -20,7 +20,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary> /// </summary>
public Rect Box { get; set; } = Rect.zero; public Rect Box { get; set; } = Rect.zero;
public override void OnGUI() public void OnGUI()
{ {
//if (Texture == null) return; //if (Texture == null) return;
if (automaticLayout) 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> /// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Interface.IMGUI.Image"/> class with automatic layout. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Interface.IMGUI.Image"/> class with automatic layout.
/// </summary> /// </summary>
/// <param name="texture">Image to display.</param> /// <param name="texture">Image to display.</param>
/// <param name="name">The element's name.</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; automaticLayout = true;
Texture = texture; 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> /// <summary>

View file

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

View file

@ -28,7 +28,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary> /// </summary>
public event EventHandler<string> OnEdit; public event EventHandler<string> OnEdit;
public override void OnGUI() public void OnGUI()
{ {
string editedText = null; string editedText = null;
if (automaticLayout) 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> /// <summary>
/// Initialize the text input field with automatic layout. /// Initialize the text input field with automatic layout.
/// </summary> /// </summary>
/// <param name="initialText">Initial text in the input field.</param> /// <param name="initialText">Initial text in the input field.</param>
/// <param name="name">The text field's name.</param> /// <param name="name">The text field's name.</param>
/// <param name="multiline">Allow multiple lines?</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; this.Multiline = multiline;
automaticLayout = true; automaticLayout = true;
text = initialText ?? ""; text = initialText ?? "";
if (name == null)
{
this.Name = typeof(Text).FullName + "::" + text;
}
else
{
this.Name = name;
}
IMGUIManager.AddElement(this);
} }
/// <summary> /// <summary>

View file

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

View file

@ -3,11 +3,13 @@ using System.Reflection;
using HarmonyLib; using HarmonyLib;
using RobocraftX; using RobocraftX;
using RobocraftX.Schedulers;
using RobocraftX.Services; using RobocraftX.Services;
using Svelto.Context; using Svelto.Context;
using Svelto.Tasks.ExtraLean;
using TechbloxModdingAPI.App; using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Events;
using TechbloxModdingAPI.Tasks; using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -49,42 +51,34 @@ namespace TechbloxModdingAPI
harmony.PatchAll(currentAssembly); harmony.PatchAll(currentAssembly);
} }
catch (Exception e) catch (Exception e)
{ { //Can't use ErrorBuilder or Logging.LogException (which eventually uses ErrorBuilder) yet
HandleError(e, "Failed to patch Techblox. Attempting to patch to display error...", OnPatchError); 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; return;
} }
try // init utility
{ Logging.MetaDebugLog($"Initializing Utility");
// init utility Utility.GameState.Init();
Logging.MetaDebugLog($"Initializing Utility"); // init block implementors
Utility.GameState.Init(); Logging.MetaDebugLog($"Initializing Blocks");
// init block implementors // init input
Logging.MetaDebugLog($"Initializing Blocks"); Input.FakeInput.Init();
// init input // init object-oriented classes
Input.FakeInput.Init(); Player.Init();
// init object-oriented classes Block.Init();
Player.Init(); BlockGroup.Init();
Block.Init(); Wire.Init();
BlockGroup.Init(); Logging.MetaDebugLog($"Initializing Client");
Wire.Init(); Client.Init();
// init client Game.Init();
Logging.MetaDebugLog($"Initializing Client"); // init UI
Client.Init(); Interface.IMGUI.Constants.Init();
Game.Init(); Interface.IMGUI.IMGUIManager.Init();
// init UI Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized");
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> /// <summary>
@ -104,7 +98,7 @@ namespace TechbloxModdingAPI
} }
Scheduler.Dispose(); Scheduler.Dispose();
var currentAssembly = Assembly.GetExecutingAssembly(); var currentAssembly = Assembly.GetExecutingAssembly();
harmony.UnpatchSelf(); harmony.UnpatchAll(currentAssembly.GetName().Name);
harmony = null; harmony = null;
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} shutdown"); Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} shutdown");
} }
@ -115,26 +109,5 @@ namespace TechbloxModdingAPI
ErrorBuilder.DisplayMustQuitError("Failed to patch Techblox!\n" + 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."); "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

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

View file

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

View file

@ -46,7 +46,7 @@ namespace TechbloxModdingAPI.Persistence
public bool Serialize(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer) public bool Serialize(ref ISerializationData serializationData, EntitiesDB entitiesDB, IEntitySerialization entitySerializer)
{ {
serializationData.data.IncreaseCapacityBy(4u); serializationData.data.ExpandBy(4u);
BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out int count), serializationData.dataPos); BinaryBufferWriter bbw = new BinaryBufferWriter(serializationData.data.ToArrayFast(out int count), serializationData.dataPos);
EGID[] toSerialize = getEntitiesToSerialize(entitiesDB); EGID[] toSerialize = getEntitiesToSerialize(entitiesDB);
bbw.Write((uint)toSerialize.Length); bbw.Write((uint)toSerialize.Length);

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 System;
using Gamecraft.Wires;
using RobocraftX.Character; using RobocraftX.Character;
using RobocraftX.Character.Movement; using RobocraftX.Character.Movement;
using Unity.Mathematics; using Unity.Mathematics;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.Common.Players; using RobocraftX.Common.Players;
using RobocraftX.GUI.Wires;
using RobocraftX.Physics; using RobocraftX.Physics;
using Svelto.ECS; using Svelto.ECS;
using Techblox.BuildingDrone; using Techblox.BuildingDrone;
using Techblox.Camera; using Techblox.Camera;
using Techblox.Character;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Players; using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -21,11 +18,10 @@ namespace TechbloxModdingAPI
/// <summary> /// <summary>
/// An in-game player character. Any Leo you see is a player. /// An in-game player character. Any Leo you see is a player.
/// </summary> /// </summary>
public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID> public class Player : IEquatable<Player>, IEquatable<EGID>
{ {
// static functionality // static functionality
private static readonly PlayerEngine playerEngine = new PlayerEngine(); private static PlayerEngine playerEngine = new PlayerEngine();
private static readonly PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine();
private static Player localPlayer; private static Player localPlayer;
/// <summary> /// <summary>
@ -65,32 +61,23 @@ namespace TechbloxModdingAPI
} }
/// <summary> /// <summary>
/// Returns the current player belonging to this client. It will be different after entering/leaving simulation. /// Returns the current player belonging to this client.
/// May return null if the local player doesn't exist.
/// </summary> /// </summary>
public static Player LocalPlayer public static Player LocalPlayer
{ {
get get
{ {
var playerId = playerEngine.GetLocalPlayer(); if (localPlayer == null || localPlayer.Id != playerEngine.GetLocalPlayer())
if (playerId == uint.MaxValue) return null; localPlayer = new Player(PlayerType.Local);
if (localPlayer == null || localPlayer.Id != playerId)
localPlayer = GetInstance(playerId);
return localPlayer; return localPlayer;
} }
} }
internal static Player GetInstance(uint id)
{
return EcsObjectBase.GetInstance(new EGID(id, CharacterExclusiveGroups.OnFootGroup),
e => new Player(e.entityID));
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary> /// </summary>
/// <param name="id">The player's unique identifier.</param> /// <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; this.Id = id;
if (!Exists(id)) if (!Exists(id))
@ -104,32 +91,22 @@ namespace TechbloxModdingAPI
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary> /// </summary>
/// <param name="player">The player type. Chooses the first available player matching the criteria.</param> /// <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)
switch (player) {
{ case PlayerType.Local:
case PlayerType.Local: this.Id = playerEngine.GetLocalPlayer();
id = playerEngine.GetLocalPlayer(); break;
break; case PlayerType.Remote:
case PlayerType.Remote: this.Id = playerEngine.GetRemotePlayer();
id = playerEngine.GetRemotePlayer(); break;
break; }
default: if (this.Id == uint.MaxValue)
id = uint.MaxValue; {
break; throw new PlayerNotFoundException($"No player of {player} type exists");
} }
this.Type = player;
if (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 // object fields & properties
@ -145,7 +122,7 @@ namespace TechbloxModdingAPI
/// The player's unique identifier. /// The player's unique identifier.
/// </summary> /// </summary>
/// <value>The identifier.</value> /// <value>The identifier.</value>
public new uint Id { get; } public uint Id { get; }
/// <summary> /// <summary>
/// The player's current position. /// The player's current position.
@ -195,8 +172,10 @@ namespace TechbloxModdingAPI
/// The player's mass. /// The player's mass.
/// </summary> /// </summary>
/// <value>The mass.</value> /// <value>The mass.</value>
[Obsolete] // We cannot get it clientside or something public float Mass =>
public float Mass => 0; 1f / playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().physicsMass.InverseMass;
private float _ping = -1f;
/// <summary> /// <summary>
/// The player's latest network ping time. /// The player's latest network ping time.
@ -206,7 +185,12 @@ namespace TechbloxModdingAPI
{ {
get 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. /// The player's initial health when entering Simulation (aka Time Running) mode.
/// </summary> /// </summary>
/// <value>The initial health.</value> /// <value>The initial health.</value>
[Obsolete("We can no longer get initial health, returns max health.")]
public float InitialHealth public float InitialHealth
{ {
get get
{ {
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id); var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
return opt ? opt.Get().maxHealth : -1f; return opt ? opt.Get().initialHealth : -1f;
} }
set => playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id).Get().maxHealth = value; set => playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get().initialHealth = value;
} }
/// <summary> /// <summary>
@ -234,25 +217,30 @@ namespace TechbloxModdingAPI
{ {
get get
{ {
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id); var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
return opt ? opt.Get().currentHealth : -1f; return opt ? opt.Get().currentHealth : -1f;
} }
set => playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id).Get().currentHealth = value; set => playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get().currentHealth = value;
} }
/// <summary> /// <summary>
/// Whether this <see cref="T:TechbloxModdingAPI.Player"/> is damageable. /// Whether this <see cref="T:TechbloxModdingAPI.Player"/> is damageable.
/// </summary> /// </summary>
/// <value><c>true</c> if damageable; otherwise, <c>false</c>.</value> /// <value><c>true</c> if damageable; otherwise, <c>false</c>.</value>
[Obsolete("Players are probably always damageable")]
public bool Damageable public bool Damageable
{ {
get => true; get
{
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
return opt.Get().canTakeDamageStat;
}
// ReSharper disable once ValueParameterNotUsed
set 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. /// The player's lives when initially entering Simulation (aka Time Running) mode.
/// </summary> /// </summary>
/// <value>The initial lives.</value> /// <value>The initial lives.</value>
[Obsolete("The player has infinite lives")]
public uint InitialLives 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 => playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id).Get().initialLives = value;
set { }
} }
/// <summary> /// <summary>
/// The player's current lives in Simulation (aka Time Running) mode. /// The player's current lives in Simulation (aka Time Running) mode.
/// </summary> /// </summary>
/// <value>The current lives.</value> /// <value>The current lives.</value>
[Obsolete("The player has infinite lives")]
public uint CurrentLives 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 => playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id).Get().currentLives = value;
set { }
} }
/*/// <summary> /*/// <summary>
@ -310,7 +302,7 @@ namespace TechbloxModdingAPI
get get
{ {
var optstruct = playerEngine.GetCharacterStruct<EquippedPartStruct>(Id); 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> /// <summary>
/// The player's mode in time stopped mode, determining what they place. /// The player's mode in time stopped mode, determining what they place.
/// </summary> /// </summary>
public PlayerBuildingMode BuildingMode => (PlayerBuildingMode)Math.Log((double)playerEngine public PlayerBuildingMode BuildingMode => (PlayerBuildingMode) playerEngine
.GetCharacterStruct<TimeStoppedModeComponent>(Id).Get().timeStoppedContext, 2); // It's a bit field in game now .GetCharacterStruct<TimeStoppedModeComponent>(Id).Get().timeStoppedContext;
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")
};
/// <summary> /// <summary>
/// Whether the player is sprinting. /// Whether the player is sprinting.
@ -443,39 +423,6 @@ namespace TechbloxModdingAPI
playerEngine.SetLocation(Id, location, exitSeat: exitSeat); 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> /// <summary>
/// Returns the block the player is currently looking at in build mode. /// Returns the block the player is currently looking at in build mode.
/// </summary> /// </summary>
@ -484,8 +431,7 @@ namespace TechbloxModdingAPI
public Block GetBlockLookedAt(float maxDistance = -1f) public Block GetBlockLookedAt(float maxDistance = -1f)
{ {
var egid = playerEngine.GetThingLookedAt(Id, maxDistance); var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
return egid != default && egid.groupID != CommonExclusiveGroups.SIMULATION_BODIES_GROUP return egid != EGID.Empty && egid.groupID != CommonExclusiveGroups.SIMULATION_BODIES_GROUP
&& egid.groupID != WiresGUIExclusiveGroups.WireGroup
? Block.New(egid) ? Block.New(egid)
: null; : null;
} }
@ -498,22 +444,8 @@ namespace TechbloxModdingAPI
public SimBody GetSimBodyLookedAt(float maxDistance = -1f) public SimBody GetSimBodyLookedAt(float maxDistance = -1f)
{ {
var egid = playerEngine.GetThingLookedAt(Id, maxDistance); var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
return egid != default && egid.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP return egid != EGID.Empty && egid.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP
? EcsObjectBase.GetInstance(egid, e => new SimBody(e)) ? new SimBody(egid)
: 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))
: null; : null;
} }
@ -526,15 +458,6 @@ namespace TechbloxModdingAPI
return playerEngine.GetSelectedBlocks(Id); 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) public bool Equals(Player other)
{ {
if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(null, other)) return false;
@ -562,17 +485,11 @@ namespace TechbloxModdingAPI
return (int) Id; 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 methods
internal static void Init() internal static void Init()
{ {
Utility.GameEngineManager.AddGameEngine(playerEngine); Utility.GameEngineManager.AddGameEngine(playerEngine);
Utility.GameEngineManager.AddGameEngine(playerEventsEngine);
} }
} }
} }

View file

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

View file

@ -1,5 +1,4 @@
using System; using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
using RobocraftX.Character; using RobocraftX.Character;
using RobocraftX.Character.Movement; using RobocraftX.Character.Movement;
@ -8,24 +7,19 @@ using RobocraftX.Common.Input;
using RobocraftX.CR.MachineEditing.BoxSelect; using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.Physics; using RobocraftX.Physics;
using RobocraftX.Blocks.Ghost; using RobocraftX.Blocks.Ghost;
using RobocraftX.Common; using Gamecraft.GUI.HUDFeedbackBlocks;
using RobocraftX.Multiplayer;
using RobocraftX.SimulationModeState;
using Svelto.ECS; using Svelto.ECS;
using Techblox.Camera; using Techblox.Camera;
using Unity.Mathematics; using Unity.Mathematics;
using Svelto.ECS.DataStructures; using Svelto.ECS.DataStructures;
using Techblox.BuildingDrone; using Techblox.BuildingDrone;
using Techblox.Character;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Players namespace TechbloxModdingAPI.Players
{ {
internal class PlayerEngine : IFunEngine internal class PlayerEngine : IApiEngine, IFactoryEngine
{ {
public string Name { get; } = "TechbloxModdingAPIPlayerGameEngine"; public string Name { get; } = "TechbloxModdingAPIPlayerGameEngine";
@ -33,7 +27,7 @@ namespace TechbloxModdingAPI.Players
public bool isRemovable => false; public bool isRemovable => false;
public IEntityFunctions Functions { get; set; } public IEntityFactory Factory { set; private get; }
private bool isReady = false; private bool isReady = false;
@ -50,10 +44,10 @@ namespace TechbloxModdingAPI.Players
public uint GetLocalPlayer() public uint GetLocalPlayer()
{ {
if (!isReady) return uint.MaxValue; if (!isReady) return uint.MaxValue;
var (localPlayers, count) = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers); var localPlayers = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers).ToBuffer();
if (count > 0) if (localPlayers.count > 0)
{ {
return localPlayers[0].ID.entityID; return localPlayers.buffer[0].ID.entityID;
} }
return uint.MaxValue; return uint.MaxValue;
} }
@ -61,10 +55,10 @@ namespace TechbloxModdingAPI.Players
public uint GetRemotePlayer() public uint GetRemotePlayer()
{ {
if (!isReady) return uint.MaxValue; if (!isReady) return uint.MaxValue;
var (localPlayers, count) = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers); var localPlayers = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers).ToBuffer();
if (count > 0) if (localPlayers.count > 0)
{ {
return localPlayers[0].ID.entityID; return localPlayers.buffer[0].ID.entityID;
} }
return uint.MaxValue; return uint.MaxValue;
} }
@ -107,16 +101,26 @@ namespace TechbloxModdingAPI.Players
return false; return false;
if (group == CharacterExclusiveGroups.InPilotSeatGroup && exitSeat) 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; rbesOpt.Get().position = location;
return true; 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) public bool IsDead(uint playerId)
{ {
if (entitiesDB == null) return true; if (entitiesDB == null) return true;
return entitiesDB.Exists<RigidBodyEntityStruct>(playerId, CharacterExclusiveGroups.DeadGroup); return entitiesDB.Exists<RigidBodyEntityStruct>(playerId, CharacterExclusiveGroups.DeadCharacters);
} }
// reusable methods // reusable methods
@ -163,29 +167,28 @@ namespace TechbloxModdingAPI.Players
public EGID GetThingLookedAt(uint playerId, float maxDistance = -1f) public EGID GetThingLookedAt(uint playerId, float maxDistance = -1f)
{ {
var opt = GetCameraStruct<PhysicCameraRayCastEntityStruct>(playerId); var opt = GetCameraStruct<PhysicCameraRayCastEntityStruct>(playerId);
if (!opt) return default; if (!opt) return EGID.Empty;
PhysicCameraRayCastEntityStruct rayCast = opt; PhysicCameraRayCastEntityStruct rayCast = opt;
EGID physicCameraEgid = new EGID(playerId, CameraExclusiveGroups.PhysicCameraGroup);
float distance = maxDistance < 0 float distance = maxDistance < 0
? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast, physicCameraEgid, ? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast,
GhostBlockUtils.GhostCastMethod.GhostCastProportionalToBlockSize) GhostBlockUtils.GhostCastMethod.GhostCastProportionalToBlockSize)
: maxDistance; : maxDistance;
if (rayCast.hit && rayCast.distance <= distance) 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) public unsafe Block[] GetSelectedBlocks(uint playerid)
{ {
if (!entitiesDB.Exists<BoxSelectStateEntityStruct>(playerid, if (!entitiesDB.Exists<BoxSelectStateEntityStruct>(playerid,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup)) BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup))
return Array.Empty<Block>(); return new Block[0];
var state = entitiesDB.QueryEntity<BoxSelectStateEntityStruct>(playerid, var state = entitiesDB.QueryEntity<BoxSelectStateEntityStruct>(playerid,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup); BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup);
var blocks = entitiesDB.QueryEntity<SelectedBlocksStruct>(playerid, var blocks = entitiesDB.QueryEntity<SelectedBlocksStruct>(playerid,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup); BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup);
if (!state.active) return Array.Empty<Block>(); if (!state.active) return new Block[0];
var pointer = (EGID*) blocks.selectedBlocks.ToPointer(); var pointer = (EGID*) blocks.selectedBlocks.ToPointer();
var ret = new Block[blocks.count]; var ret = new Block[blocks.count];
for (int j = 0; j < blocks.count; j++) for (int j = 0; j < blocks.count; j++)
@ -196,79 +199,5 @@ namespace TechbloxModdingAPI.Players
return ret; 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;
using System.Collections.Generic;
using Svelto.Tasks;
using Svelto.Tasks.Enumerators;
using Unity.Mathematics; using Unity.Mathematics;
using TechbloxModdingAPI.App; using TechbloxModdingAPI;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Players namespace TechbloxModdingAPI.Players
{ {
@ -29,63 +24,13 @@ namespace TechbloxModdingAPI.Players
[APITestCase(TestType.EditMode)] [APITestCase(TestType.EditMode)]
public static void PositionTest() 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.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.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; 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."); 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)] [APITestCase(TestType.Menu)]
public static void InvalidStateTest() public static void InvalidStateTest()
{ {

View file

@ -6,7 +6,6 @@ using UnityEngine;
using Gamecraft.Damage; using Gamecraft.Damage;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.Physics; using RobocraftX.Physics;
using Techblox.TimeRunning.Clusters;
namespace TechbloxModdingAPI namespace TechbloxModdingAPI
{ {
@ -15,20 +14,20 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public class SimBody : EcsObjectBase, IEquatable<SimBody>, IEquatable<EGID> public class SimBody : EcsObjectBase, IEquatable<SimBody>, IEquatable<EGID>
{ {
public override EGID Id { get; }
/// <summary> /// <summary>
/// The cluster this chunk belongs to, or null if no cluster destruction manager present or the chunk doesn't exist. /// 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. /// Get the SimBody from a Block if possible for good performance here.
/// </summary> /// </summary>
public Cluster Cluster => cluster ??= clusterId == uint.MaxValue // Return cluster or if it's null then set it public Cluster Cluster => cluster ?? (cluster = clusterId == uint.MaxValue ? Block.BlockEngine.GetCluster(Id.entityID) : new Cluster(clusterId));
? 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
private Cluster cluster; private Cluster cluster;
private readonly uint clusterId = uint.MaxValue; 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)) 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 public float Mass
{ {
get => 0f; get => math.rcp(GetStruct().physicsMass.InverseMass);
//set => GetStruct().physicsMass.InverseMass = math.rcp(value); //set => GetStruct().physicsMass.InverseMass = math.rcp(value);
} }
public float3 CenterOfMass public float3 CenterOfMass
{ {
get => 0f; //TODO get => GetStruct().physicsMass.CenterOfMass;
//set => GetStruct().physicsMass.CenterOfMass = value; //set => GetStruct().physicsMass.CenterOfMass = value;
} }
@ -94,20 +92,20 @@ namespace TechbloxModdingAPI
public float InitialHealth public float InitialHealth
{ {
get => 0f; get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth;
set { } set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth = value;
} }
public float CurrentHealth public float CurrentHealth
{ {
get => 0f; get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth;
set { } set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth = value;
} }
public float HealthMultiplier public float HealthMultiplier
{ {
get => 0f; get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier;
set { } set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier = value;
} }
/// <summary> /// <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 get
{ {
return RobocraftX.Schedulers.ClientLean.UIScheduler; return RobocraftX.Schedulers.Lean.UIScheduler;
} }
} }
@ -28,7 +28,7 @@ namespace TechbloxModdingAPI.Tasks
{ {
get get
{ {
return RobocraftX.Schedulers.ClientExtraLean.UIScheduler; return RobocraftX.Schedulers.ExtraLean.UIScheduler;
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -1,84 +1,354 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using HarmonyLib;
using RobocraftX.FrontEnd;
using TechbloxModdingAPI.App; using TechbloxModdingAPI.App;
using HarmonyLib;
using IllusionInjector;
// test
using RobocraftX.FrontEnd;
using Unity.Mathematics;
using UnityEngine;
using RobocraftX.Common.Input;
using Svelto.Tasks;
using Svelto.Tasks.Lean;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Commands; using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Tests namespace TechbloxModdingAPI.Tests
{ {
#if DEBUG // The API should be loaded by other plugins, but it can be used by itself for testing #if DEBUG
[BepInPlugin("org.exmods.TechbloxModdingAPIPluginTest", PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] // unused by design
[BepInProcess("Techblox.exe")] /// <summary>
public class TechbloxModdingAPIPluginTest : BaseUnityPlugin /// 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}");
};
CommandBuilder.Builder() private static Harmony harmony { get; set; }
.Name("Exit")
.Description("Close Techblox immediately, without any prompts")
.Action(() => { UnityEngine.Application.Quit(); })
.Build();
CommandBuilder.Builder() public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
.Name("SetFOV")
.Description("Set the player camera's field of view")
.Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
.Build();
Game.AddPersistentDebugInfo("InstalledMods", InstalledMods); public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
// Plugin startup logic public string HarmonyID { get; } = "org.git.exmods.modtainers.techbloxmoddingapi";
Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!");
#if TEST public override void OnApplicationQuit()
TestRoot.RunTests();
#endif
}
private void OnDestroy()
{ {
Main.Shutdown(); Main.Shutdown();
} }
private string modsString; 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")
.Action(() => { UnityEngine.Application.Quit(); })
.Build();
CommandBuilder.Builder()
.Name("SetFOV")
.Description("Set the player camera's field of view")
.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);
}
// 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))*/
/*Game.Enter += (sender, e) =>
{
ushort lastKey = ushort.MaxValue;
foreach (var kv in FullGameFields._dataDb.GetValues<CubeListData>()
.OrderBy(kv=>ushort.Parse(kv.Key)))
{
var data = (CubeListData) kv.Value;
ushort currentKey = ushort.Parse(kv.Key);
var toReplace = new Dictionary<string, string>
{
{"Scalable", ""}, {"Qtr", "Quarter"}, {"RNeg", "Rounded Negative"},
{"Neg", "Negative"}, {"Tetra", "Tetrahedron"},
{"RWedge", "Rounded Wedge"}, {"RTetra", "Rounded Tetrahedron"}
};
string name = LocalizationService.Localize(data.CubeNameKey).Replace(" ", "");
foreach (var rkv in toReplace)
{
name = Regex.Replace(name, "([^A-Za-z])" + rkv.Key + "([^A-Za-z])", "$1" + rkv.Value + "$2");
}
Console.WriteLine($"{name}{(currentKey != lastKey + 1 ? $" = {currentKey}" : "")},");
lastKey = currentKey;
}
};*/
/*Game.Enter += (sender, e) =>
{
Console.WriteLine("Materials:\n" + FullGameFields._dataDb.GetValues<MaterialPropertiesData>()
.Select(kv => $"{kv.Key}: {((MaterialPropertiesData) kv.Value).Name}")
.Aggregate((a, b) => a + "\n" + b));
};*/
CommandBuilder.Builder("takeScreenshot", "Enables the screenshot taker")
.Action(() =>
{
Game.CurrentGame().EnableScreenshotTaker();
}).Build();
CommandBuilder.Builder("testPositionDefault", "Tests the Block.Position property's default value.")
.Action(() =>
{
IEnumerator<TaskContract> Loop()
{
for (int i = 0; i < 2; i++)
{
Console.WriteLine("A");
var block = Block.PlaceNew(BlockIDs.Cube, 1);
Console.WriteLine("B");
while (!block.Exists)
yield return Yield.It;
Console.WriteLine("C");
block.Remove();
Console.WriteLine("D");
while (block.Exists)
yield return Yield.It;
Console.WriteLine("E - Pos: " + block.Position);
block.Position = 4;
Console.WriteLine("F - Pos: " + block.Position);
}
}
Loop().RunOn(Scheduler.leanRunner);
}).Build();
#if TEST
TestRoot.RunTests();
#endif
}
private string modsString;
private string InstalledMods() private string InstalledMods()
{ {
if (modsString != null) return modsString; if (modsString != null) return modsString;
StringBuilder sb = new StringBuilder("Installed mods:"); StringBuilder sb = new StringBuilder("Installed mods:");
foreach (var (_, plugin) in Chainloader.PluginInfos) foreach (var plugin in PluginManager.Plugins)
sb.Append("\n" + plugin.Metadata.Name + " - " + plugin.Metadata.Version); sb.Append("\n" + plugin.Name + " - " + plugin.Version);
return modsString = sb.ToString(); return modsString = sb.ToString();
}
}
[HarmonyPatch]
public class MinimumSpecsPatch
{
public static bool Prefix(ref bool __result)
{
__result = true;
return false;
} }
public static MethodInfo TargetMethod() public override void OnUpdate()
{ {
return ((Func<bool>) MinimumSpecsCheck.CheckRequirementsMet).Method; if (UnityEngine.Input.GetKeyDown(KeyCode.End))
{
Console.WriteLine("Pressed button to toggle console");
FakeInput.CustomInput(new LocalCosmeticInputEntityComponent {commandLineToggleInput = true});
}
}
[HarmonyPatch]
public class MinimumSpecsPatch
{
public static bool Prefix()
{
return false;
}
public static MethodInfo TargetMethod()
{
return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method;
}
} }
} }
#endif #endif
} }

View file

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

View file

@ -1,5 +1,7 @@
using System; using System;
using BepInEx.Bootstrap;
using IllusionInjector;
using IllusionPlugin;
namespace TechbloxModdingAPI.Utility namespace TechbloxModdingAPI.Utility
{ {
@ -13,11 +15,11 @@ namespace TechbloxModdingAPI.Utility
/// </summary> /// </summary>
/// <returns>The plugin.</returns> /// <returns>The plugin.</returns>
/// <param name="name">The plugin's name.</param> /// <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; return plugin;
} }
@ -33,15 +35,45 @@ namespace TechbloxModdingAPI.Utility
/// <param name="name">The plugin's name.</param> /// <param name="name">The plugin's name.</param>
public static Version GetPluginVersion(string name) public static Version GetPluginVersion(string name)
{ {
var plugin = GetPlugin(name); IPlugin plugin = GetPlugin(name);
if (plugin != null) { if (plugin != null) {
try try
{ {
return plugin.Metadata.Version; return new Version(plugin.Version);
} catch (Exception e) when (e is ArgumentException or FormatException or OverflowException) {} } 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 plugin.GetType().Assembly.GetName().Version;
} }
return null; 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 HarmonyLib;
using RobocraftX; using RobocraftX;
using RobocraftX.CR.MainGame; using RobocraftX.Common.Utilities;
using RobocraftX.GUI; using RobocraftX.GUI;
using RobocraftX.Multiplayer; using RobocraftX.Multiplayer;
using RobocraftX.Rendering;
using Svelto.Context; using Svelto.Context;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.GUI; using Svelto.ECS.Schedulers;
using Techblox.GameSelection;
using UnityEngine; using UnityEngine;
using Unity.Entities; using Unity.Entities;
using Unity.Physics.Systems; 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 public static BuildPhysicsWorld _physicsWorldSystem
{ {
get get
@ -98,6 +112,14 @@ namespace TechbloxModdingAPI.Utility
} }
} }
public static PhysicsUtility _physicsUtility
{
get
{
return (PhysicsUtility)fgcr?.Field("_physicsUtility").GetValue();
}
}
/*public static UnityEntitySubmissionScheduler _frontEndSubmissionScheduler /*public static UnityEntitySubmissionScheduler _frontEndSubmissionScheduler
{ {
get get
@ -122,11 +144,11 @@ namespace TechbloxModdingAPI.Utility
} }
} }
public static ECSMainGameResourceManagers _managers public static ECSGameObjectResourceManager _eCsGameObjectResourceManager
{ {
get 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; private static Traverse fgcr;
public static void Init(FullGameCompositionRoot instance) 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.Linq;
using System.Text;
using System.Threading.Tasks;
using RobocraftX.StateSync; using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
@ -23,10 +26,10 @@ namespace TechbloxModdingAPI.Utility
{ {
Logging.MetaDebugLog($"Registering Game IApiEngine {engine.Name}"); Logging.MetaDebugLog($"Registering Game IApiEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine); _lastEngineRoot.AddEngine(engine);
if (engine is IFactoryEngine factoryEngine) if (typeof(IFactoryEngine).IsAssignableFrom(engine.GetType()))
factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory(); {
if (engine is IFunEngine funEngine) ((IFactoryEngine)engine).Factory = _lastEngineRoot.GenerateEntityFactory();
funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions(); }
} }
} }
@ -63,7 +66,6 @@ namespace TechbloxModdingAPI.Utility
var enginesRoot = helper.enginesRoot; var enginesRoot = helper.enginesRoot;
_lastEngineRoot = enginesRoot; _lastEngineRoot = enginesRoot;
IEntityFactory factory = enginesRoot.GenerateEntityFactory(); IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions();
foreach (var key in _gameEngines.Keys) foreach (var key in _gameEngines.Keys)
{ {
Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}"); Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}");
@ -73,8 +75,6 @@ namespace TechbloxModdingAPI.Utility
enginesRoot.AddEngine(_gameEngines[key]); enginesRoot.AddEngine(_gameEngines[key]);
if (_gameEngines[key] is IFactoryEngine factEngine) if (_gameEngines[key] is IFactoryEngine factEngine)
factEngine.Factory = factory; factEngine.Factory = factory;
if (_gameEngines[key] is IFunEngine funEngine)
funEngine.Functions = functions;
} }
} }
} }

View file

@ -163,7 +163,7 @@ namespace TechbloxModdingAPI.Utility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CommandLog(string msg) public static void CommandLog(string msg)
{ {
Log(msg); uREPL.Log.Output(msg);
} }
/// <summary> /// <summary>
@ -179,7 +179,7 @@ namespace TechbloxModdingAPI.Utility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CommandLogError(string msg) public static void CommandLogError(string msg)
{ {
LogError(msg); uREPL.Log.Error(msg);
} }
/// <summary> /// <summary>
@ -195,7 +195,7 @@ namespace TechbloxModdingAPI.Utility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CommandLogWarning(string msg) 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}"); Logging.MetaDebugLog($"Registering Menu IApiEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine); _lastEngineRoot.AddEngine(engine);
if (engine is IFactoryEngine factoryEngine) if (typeof(IFactoryEngine).IsAssignableFrom(engine.GetType()))
factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory(); {
if (engine is IFunEngine funEngine) ((IFactoryEngine)engine).Factory = _lastEngineRoot.GenerateEntityFactory();
funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions(); }
} }
} }
@ -65,13 +65,14 @@ namespace TechbloxModdingAPI.Utility
{ {
_lastEngineRoot = enginesRoot; _lastEngineRoot = enginesRoot;
IEntityFactory factory = enginesRoot.GenerateEntityFactory(); IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions();
foreach (var key in _menuEngines.Keys) foreach (var key in _menuEngines.Keys)
{ {
Logging.MetaDebugLog($"Registering Menu IApiEngine {_menuEngines[key].Name}"); Logging.MetaDebugLog($"Registering Menu IApiEngine {_menuEngines[key].Name}");
enginesRoot.AddEngine(_menuEngines[key]); enginesRoot.AddEngine(_menuEngines[key]);
if (_menuEngines[key] is IFactoryEngine factEngine) factEngine.Factory = factory; if (_menuEngines[key] is IFactoryEngine factEngine)
if(_menuEngines[key] is IFunEngine funEngine) funEngine.Functions = functions; {
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;
using System.Runtime.InteropServices;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
namespace TechbloxModdingAPI.Utility 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 State state;
private readonly uint index; private readonly uint index;
private NB<T> array; private NB<T> array;
@ -14,22 +14,19 @@ namespace TechbloxModdingAPI.Utility
private readonly EntityInitializer initializer; private readonly EntityInitializer initializer;
//The possible fields are: (index && (array || managedArray)) || 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; state = State.Native;
this.array = array; this.array = array;
this.index = index; this.index = index;
this.entityId = entityId;
initializer = default; initializer = default;
managedArray = default;
} }
public OptionalRef(MB<T> array, uint index, EGID entityId = default) public OptionalRef(MB<T> array, uint index)
{ {
state = State.Managed; state = State.Managed;
managedArray = array; managedArray = array;
this.index = index; this.index = index;
this.entityId = entityId;
initializer = default; initializer = default;
this.array = default; this.array = default;
} }
@ -39,9 +36,8 @@ namespace TechbloxModdingAPI.Utility
/// </summary> /// </summary>
/// <param name="obj">The object with the initializer</param> /// <param name="obj">The object with the initializer</param>
/// <param name="unmanaged">Whether the struct is unmanaged</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) if (obj.InitData.Valid)
{ {
initializer = obj.InitData.Initializer(obj.Id); initializer = obj.InitData.Initializer(obj.Id);
@ -54,7 +50,6 @@ namespace TechbloxModdingAPI.Utility
} }
array = default; array = default;
index = default; index = default;
managedArray = default;
} }
/// <summary> /// <summary>
@ -65,34 +60,17 @@ namespace TechbloxModdingAPI.Utility
{ {
CompRefCache.Default = default; //The default value can be changed by mods CompRefCache.Default = default; //The default value can be changed by mods
if (state == State.Empty) return ref CompRefCache.Default; 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]; if ((state & State.Native) != State.Empty) return ref array[index];
return ref managedArray[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 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 T(OptionalRef<T> opt) => opt.Get();
public static implicit operator bool(OptionalRef<T> opt) => opt.state != State.Empty; public static implicit operator bool(OptionalRef<T> opt) => opt.state != State.Empty;
public static implicit operator EGID(OptionalRef<T> opt) => opt.entityId;
/// <summary> /// <summary>
/// Creates an instance of a struct T that can be referenced. /// Creates an instance of a struct T that can be referenced.
/// </summary> /// </summary>

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);
}
}
}

View file

@ -38,7 +38,7 @@ PROJECT_NAME = "TechbloxModdingAPI"
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = "v2.2.0" PROJECT_NUMBER = "v2.0.0"
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a