Compare commits

..

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

101 changed files with 2791 additions and 8737 deletions

View file

@ -5,7 +5,7 @@ import re
# this assumes a mostly semver-complient version number # 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

@ -3,24 +3,15 @@
import argparse import argparse
from pathlib import Path, PurePath from pathlib import Path, PurePath
import re import re
import os
DLL_EXCLUSIONS_REGEX = r"(System|Microsoft|Mono|IronPython|DiscordRPC|IllusionInjector|IllusionPlugin|netstandard)\." DLL_EXCLUSIONS_REGEX = r"(System|Microsoft|Mono|IronPython|DiscordRPC)\."
def getAssemblyReferences(path): def getAssemblyReferences(path):
asmDir = Path(path) asmDir = Path(path)
result = list() result = list()
addedPath = ""
if not asmDir.exists():
addedPath = "../"
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) result.append(str(child).replace("\\", "/"))
childstr = os.path.relpath(childstr, addedPath).replace("\\", "/")
result.append(childstr)
result.sort(key=str.lower)
result = [path + "/IllusionInjector.dll", path + "/IllusionPlugin.dll"] + result # Always put it on top
return result return result
def buildReferencesXml(path): def buildReferencesXml(path):
@ -41,7 +32,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 +44,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,63 @@
using System;
using RobocraftX.GUI.MyGamesScreen;
using RobocraftX.GUI;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App
{
public class AppEngine : IFactoryEngine
{
public event EventHandler<MenuEventArgs> EnterMenu;
public event EventHandler<MenuEventArgs> ExitMenu;
public IEntityFactory Factory { set; private get; }
public string Name => "TechbloxModdingAPIAppEngine";
public bool isRemovable => false;
public EntitiesDB entitiesDB { set; private get; }
public void Dispose()
{
IsInMenu = false;
ExceptionUtil.InvokeEvent(ExitMenu, this, new MenuEventArgs { });
}
public void Ready()
{
IsInMenu = true;
ExceptionUtil.InvokeEvent(EnterMenu, this, new MenuEventArgs { });
}
// app functionality
public bool IsInMenu
{
get;
private set;
} = false;
public Game[] GetMyGames()
{
EntityCollection<MyGameDataEntityStruct> mgsevs = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
var mgsevsB = mgsevs.ToBuffer().buffer;
Game[] games = new Game[mgsevs.count];
for (int i = 0; i < mgsevs.count; i++)
{
Utility.Logging.MetaDebugLog($"Found game named {mgsevsB[i].GameName}");
games[i] = new Game(mgsevsB[i].ID);
}
return games;
}
}
public struct MenuEventArgs
{
}
}

View file

@ -14,19 +14,22 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </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;
protected static Action<object> HandleErrorClosed;
/// <summary> /// <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;
} }
} }
@ -446,15 +473,6 @@ namespace TechbloxModdingAPI.App
return blocks; return blocks;
} }
/// <summary>
/// Enable the screenshot taker for updating the game's screenshot. Breaks the pause menu in a new save.
/// </summary>
public void EnableScreenshotTaker()
{
if (!VerifyMode()) return;
gameEngine.EnableScreenshotTaker();
}
~Game() ~Game()
{ {
foreach (string id in debugIds) foreach (string id in debugIds)

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,17 @@ 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 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 +28,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 +52,7 @@ namespace TechbloxModdingAPI.App
{ {
if (async) if (async)
{ {
ExitCurrentGameAsync().RunOn(ClientLean.EveryFrameStepRunner_TimeRunningAndStopped); ExitCurrentGameAsync().RunOn(Lean.EveryFrameStepRunner_TimeRunningAndStopped);
} }
else else
{ {
@ -118,78 +92,40 @@ namespace TechbloxModdingAPI.App
public void ToggleTimeMode() public void ToggleTimeMode()
{ {
if (TimeRunningModeUtil.IsTimeStoppedMode(entitiesDB)) TimeRunningModeUtil.ToggleTimeRunningState(entitiesDB);
FakeInput.ActionInput(toggleMode: true);
else
{
IEnumerator<TaskContract> ReloadBuildModeTask()
{
SwitchAnimationUtil.Start(entitiesDB);
while (SwitchAnimationUtil.IsFadeOutActive(entitiesDB))
yield return (TaskContract)Yield.It;
FullGameFields._multiplayerParams.MultiplayerMode = MultiplayerMode.SinglePlayer;
AccessTools.Method(typeof(FullGameCompositionRoot), "ReloadGame")
.Invoke(FullGameFields.Instance, new object[] { });
}
ReloadBuildModeTask().RunOn(ClientLean.UIScheduler);
}
} }
public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid) public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid)
{ {
var allBlocks = entitiesDB.QueryEntities<BlockTagEntityStruct>(); var allBlocks = entitiesDB.QueryEntities<BlockTagEntityStruct>();
List<EGID> blockEGIDs = new List<EGID>(); List<EGID> blockEGIDs = new List<EGID>();
foreach (var ((_, ids, count), group) in allBlocks)
{
for (int i = 0; i < count; i++)
{
var id = new EGID(ids[i], group);
uint dbid;
if (filter == BlockIDs.Invalid) if (filter == BlockIDs.Invalid)
dbid = (uint)filter; {
foreach (var (blocks, _) in allBlocks)
{
var buffer = blocks.ToBuffer().buffer;
for (int i = 0; i < buffer.capacity; i++)
blockEGIDs.Add(buffer[i].ID);
}
return blockEGIDs.ToArray();
}
else else
dbid = entitiesDB.QueryEntity<DBEntityStruct>(id).DBID; {
var ownership = entitiesDB.QueryEntity<BlockOwnershipComponent>(id).BlockOwnership; foreach (var (blocks, _) in allBlocks)
if ((ownership & BlockOwnership.User) != 0 && dbid == (ulong)filter) {
blockEGIDs.Add(id); var array = blocks.ToBuffer().buffer;
for (var index = 0; index < array.capacity; index++)
{
var block = array[index];
uint dbid = entitiesDB.QueryEntity<DBEntityStruct>(block.ID).DBID;
if (dbid == (ulong) filter)
blockEGIDs.Add(block.ID);
} }
} }
return blockEGIDs.ToArray(); return blockEGIDs.ToArray();
} }
public void EnableScreenshotTaker()
{
ref var local = ref entitiesDB.QueryEntity<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker);
if (local.enabled)
return;
local.enabled = true;
entitiesDB.PublishEntityChange<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker);
}
public GameSelectionComponent GetGameData()
{
return entitiesDB.QueryEntity<GameSelectionComponent>(GameSelectionConstants.GameSelectionEGID);
}
public void Add(ref LoadingActionEntityStruct entityComponent, EGID egid)
{
}
public void Remove(ref LoadingActionEntityStruct entityComponent, EGID egid)
{ // Finished loading
if (!enteredGame) return;
enteredGame = false;
loadingFinished = true;
CheckJoinEvent();
}
private void CheckJoinEvent()
{
if (!loadingFinished || !playerJoined) return;
EnterGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID });
IsInGame = true;
} }
} }
} }

View file

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

View file

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

View file

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

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,38 @@ 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
yield return Yield.It; var blocks = Game.CurrentGame().GetBlocksInGame();
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 +111,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 +120,10 @@ namespace TechbloxModdingAPI.Blocks
yield break; yield break;
} }
try
{
property.SetValue(block, valueToUse); property.SetValue(block, valueToUse);
} object got = property.GetValue(block);
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;
@ -170,33 +134,14 @@ namespace TechbloxModdingAPI.Blocks
Assert.Pass("Setting all possible properties of all registered API block types succeeded."); Assert.Pass("Setting all possible properties of all registered API block types succeeded.");
} }
[APITestCase(TestType.EditMode)]
public static IEnumerator<TaskContract> TestDefaultValue()
{
for (int i = 0; i < 2; i++)
{ //Tests shared defaults
var block = Block.PlaceNew(BlockIDs.Cube, 1);
while (!block.Exists)
yield return Yield.It;
block.Remove();
while (block.Exists)
yield return Yield.It;
if(!Assert.Equal(block.Position, default,
$"Block position default value {block.Position} is incorrect, should be 0.",
$"Block position default value {block.Position} matches default."))
yield break;
block.Position = 4;
}
}
[APITestCase(TestType.EditMode)] [APITestCase(TestType.EditMode)]
public static void TestDampedSpring() public static void TestDampedSpring()
{ {
Block newBlock = Block.PlaceNew(BlockIDs.DampedSpring, Unity.Mathematics.float3.zero + 1); Block newBlock = Block.PlaceNew(BlockIDs.DampedSpring, Unity.Mathematics.float3.zero + 1);
DampedSpring b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler DampedSpring b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
Assert.Errorless(() => { b = (DampedSpring) newBlock; }, "Casting block to DampedSpring raised an exception: ", "Casting block to DampedSpring completed without issue."); Assert.Errorless(() => { b = (DampedSpring) newBlock; }, "Casting block to DampedSpring raised an exception: ", "Casting block to DampedSpring completed without issue.");
if (!Assert.CloseTo(b.Stiffness, 1f, $"DampedSpring.Stiffness {b.Stiffness} does not equal default value, possibly because it failed silently.", "DampedSpring.Stiffness is close enough to default.")) return; if (!Assert.CloseTo(b.SpringFrequency, 30f, $"DampedSpring.SpringFrequency {b.SpringFrequency} does not equal default value, possibly because it failed silently.", "DampedSpring.SpringFrequency is close enough to default.")) return;
if (!Assert.CloseTo(b.Damping, 0.1f, $"DampedSpring.Damping {b.Damping} does not equal default value, possibly because it failed silently.", "DampedSpring.Damping is close enough to default.")) return; if (!Assert.CloseTo(b.Damping, 30f, $"DampedSpring.Damping {b.Damping} does not equal default value, possibly because it failed silently.", "DampedSpring.Damping is close enough to default.")) return;
if (!Assert.CloseTo(b.MaxExtension, 0.3f, $"DampedSpring.MaxExtension {b.MaxExtension} does not equal default value, possibly because it failed silently.", "DampedSpring.MaxExtension is close enough to default.")) return; if (!Assert.CloseTo(b.MaxExtension, 0.3f, $"DampedSpring.MaxExtension {b.MaxExtension} does not equal default value, possibly because it failed silently.", "DampedSpring.MaxExtension is close enough to default.")) return;
} }

View file

@ -1,71 +1,47 @@
namespace TechbloxModdingAPI.Blocks using RobocraftX.Blocks;
{
using RobocraftX.Common; using RobocraftX.Common;
using Svelto.ECS; using Svelto.ECS;
namespace TechbloxModdingAPI.Blocks
public class DampedSpring : SignalingBlock
{ {
public class DampedSpring : Block
{
public DampedSpring(EGID id) : base(id)
{
}
public DampedSpring(uint id) : base(new EGID(id, CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP))
{
}
/// <summary> /// <summary>
/// Constructs a(n) DampedSpring object representing an existing block. /// The spring frequency.
/// </summary> /// </summary>
public DampedSpring(EGID egid) : public float SpringFrequency
base(egid)
{ {
get => BlockEngine.GetBlockInfo<DampedSpringReadOnlyStruct>(this).springFrequency;
set => BlockEngine.GetBlockInfo<DampedSpringReadOnlyStruct>(this).springFrequency = value;
} }
/// <summary> /// <summary>
/// Constructs a(n) DampedSpring object representing an existing block. /// The spring's maximum damping force.
/// </summary>
public DampedSpring(uint id) :
base(new EGID(id, CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP))
{
}
/// <summary>
/// Gets or sets the DampedSpring's Stiffness property. Tweakable stat.
/// </summary>
public float Stiffness
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).stiffness;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).stiffness = value;
}
}
/// <summary>
/// Gets or sets the DampedSpring's Damping property. Tweakable stat.
/// </summary> /// </summary>
public float Damping public float Damping
{ {
get get => BlockEngine.GetBlockInfo<DampedSpringReadOnlyStruct>(this).springDamping;
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).damping; set => BlockEngine.GetBlockInfo<DampedSpringReadOnlyStruct>(this).springDamping = 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 @@
namespace TechbloxModdingAPI.Blocks
{
using RobocraftX.Common; using RobocraftX.Common;
using Svelto.ECS; using Svelto.ECS;
using Techblox.EngineBlock;
namespace TechbloxModdingAPI.Blocks
{
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 public int TorqueDirection
{ {
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn = value; get => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).torqueDirection;
} set => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).torqueDirection = value;
} }
/// <summary>
/// Gets or sets the Engine's CurrentGear property. May not be saved.
/// </summary>
public int CurrentGear public int CurrentGear
{ {
get get => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).currentGear;
{ set => BlockEngine.GetBlockInfo<EngineBlockComponent>(this).currentGear = value;
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
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentTorque;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentTorque = value;
}
}
/// <summary>
/// Gets or sets the Engine's TotalWheelVelocityAV property. May not be saved.
/// </summary>
public float TotalWheelVelocityAV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityAV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityAV = value;
}
}
/// <summary>
/// Gets or sets the Engine's TotalWheelVelocityLV property. May not be saved.
/// </summary>
public float TotalWheelVelocityLV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityLV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityLV = value;
}
}
/// <summary>
/// Gets or sets the Engine's TotalWheelCount property. May not be saved.
/// </summary>
public int TotalWheelCount
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelCount;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelCount = value;
}
}
/// <summary>
/// Gets or sets the Engine's LastGearUpInput property. May not be saved.
/// </summary>
public bool LastGearUpInput
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearUpInput;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearUpInput = value;
}
}
/// <summary>
/// Gets or sets the Engine's LastGearDownInput property. May not be saved.
/// </summary>
public bool LastGearDownInput
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearDownInput;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearDownInput = value;
}
}
/// <summary>
/// Gets or sets the Engine's ManualToAutoGearCoolOffCounter property. May not be saved.
/// </summary>
public float ManualToAutoGearCoolOffCounter
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).manualToAutoGearCoolOffCounter;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).manualToAutoGearCoolOffCounter = value;
}
}
/// <summary>
/// Gets or sets the Engine's Load property. May not be saved.
/// </summary>
public float Load
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).load;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).load = value;
}
}
/// <summary>
/// Gets or sets the Engine's Power property. Tweakable stat.
/// </summary>
public float Power
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).power;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).power = value;
}
}
/// <summary>
/// Gets or sets the Engine's AutomaticGears property. Tweakable stat.
/// </summary>
public bool AutomaticGears
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).automaticGears;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).automaticGears = value;
}
}
/// <summary>
/// Gets or sets the Engine's GearChangeTime property. May not be saved.
/// </summary>
public float GearChangeTime
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearChangeTime;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearChangeTime = value;
}
}
/// <summary>
/// Gets or sets the Engine's MinRpm property. May not be saved.
/// </summary>
public float MinRpm
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).minRpm;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).minRpm = value;
}
}
/// <summary>
/// Gets or sets the Engine's MaxRpm property. May not be saved.
/// </summary>
public float MaxRpm
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpm;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpm = value;
}
}
/// <summary>
/// Gets the Engine's GearDownRpms property. May not be saved.
/// </summary>
public float[] GearDownRpms
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearDownRpms.ToManagedArray<float>();
}
}
/// <summary>
/// Gets or sets the Engine's GearUpRpm property. May not be saved.
/// </summary>
public float GearUpRpm
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearUpRpm;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearUpRpm = value;
}
}
/// <summary>
/// Gets or sets the Engine's MaxRpmChange property. May not be saved.
/// </summary>
public float MaxRpmChange
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpmChange;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpmChange = value;
}
}
/// <summary>
/// Gets or sets the Engine's ManualToAutoGearCoolOffTime property. May not be saved.
/// </summary>
public float ManualToAutoGearCoolOffTime
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime = value;
}
}*/
} }
} }

View file

@ -48,8 +48,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
pickedBlock.pickedBlockEntityID = sourceID; pickedBlock.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,50 @@ 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
} /*Console.WriteLine("Materials:\n" + FullGameFields._dataDb.GetValues<MaterialPropertiesData>()
.Select(kv => $"{kv.Key}: {((MaterialPropertiesData) kv.Value).Name}")
public void UpdateBlockColor(EGID id) .Aggregate((a, b) => a + "\n" + b));*/
{
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++)
{
ref ObjectIdEntityStruct oid = ref oids[i];
if (oid.objectId != id) continue;
if (!connections.HasValue) //Would need reflection to get the group from the build group otherwise if (!connections.HasValue) //Would need reflection to get the group from the build group otherwise
connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(oid.EGID.groupID); connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(oid.ID.groupID);
//var rid = connections.Value.Entity(tag.ID.entityID).machineRigidBodyId; var rid = connections.Value.Entity(oid.ID.entityID).machineRigidBodyId;
/*foreach (var rb in ret) - TODO foreach (var rb in ret)
{ {
if (rb.Id.entityID == rid) if (rb.Id.entityID == rid)
goto DUPLICATE; //Multiple Object Identifiers on one rigid body 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 +157,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 +174,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 +190,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 +203,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 +220,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,20 @@
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.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 +42,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 +82,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)
@ -168,15 +160,10 @@ namespace TechbloxModdingAPI.Blocks.Engines
private void BuildGhostBlueprint(ICollection<Block> blocks, float3 pos, quaternion rot, uint playerID) private void BuildGhostBlueprint(ICollection<Block> blocks, float3 pos, quaternion rot, uint playerID)
{ {
GhostChildUtility.ClearGhostChildren(playerID, entitiesDB, entityFunctions); GhostChildUtility.ClearGhostChildren(playerID, entitiesDB, entityFunctions);
var bssesopt = entitiesDB.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(playerID,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup));
if (!bssesopt)
return;
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);
FullGameFields._managers.blockLabelResourceManager);
} }
} }
@ -261,77 +248,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 +257,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

@ -7,7 +7,6 @@ using HarmonyLib;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using RobocraftX.Character; using RobocraftX.Character;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.Rendering; using RobocraftX.Rendering;
using RobocraftX.Rendering.GPUI; using RobocraftX.Rendering.GPUI;
using Svelto.ECS; using Svelto.ECS;
@ -16,7 +15,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 +53,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});
@ -72,14 +70,10 @@ namespace TechbloxModdingAPI.Blocks.Engines
}); });
structInitializer.Init(new UniformBlockScaleEntityStruct {scaleFactor = 1}); structInitializer.Init(new UniformBlockScaleEntityStruct {scaleFactor = 1});
structInitializer.Get<CubeMaterialStruct>().materialId = (byte) BlockMaterial.SteelBodywork; structInitializer.Get<CubeMaterialStruct>().materialId = (byte) BlockMaterial.SteelBodywork;
var bssesopt = entitiesDB.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(playerId,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup));
if (!bssesopt)
throw new BlockException("Invalid player ID specified for block placement");
structInitializer.Init(new BlockPlacementInfoStruct structInitializer.Init(new BlockPlacementInfoStruct
{ {
loadedFromDisk = false, loadedFromDisk = false,
placedByBuildingDrone = bssesopt.Get().buildingDroneReference, placedBy = playerId,
triggerAutoWiring = autoWire && structInitializer.Has<BlockPortsStruct>() triggerAutoWiring = autoWire && structInitializer.Has<BlockPortsStruct>()
}); });

View file

@ -1,61 +1,45 @@
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 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 +59,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))
{
exists = false;
return default; return default;
var group = output }
? NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group exists = true;
: NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group;
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block); BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
if (!entitiesDB.TryQueryMappedEntities<PortEntityStruct>(group, out var mapper)) return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group);
return default;
for (uint i = 0; i < (output ? ports.outputCount : ports.inputCount); ++i)
{
uint entityID = (output ? ports.firstOutputID : ports.firstInputID) + i;
if (!mapper.TryGetArrayAndEntityIndex(entityID, out var index, out var array) ||
array[index].usage != portUsage) continue;
return new OptionalRef<PortEntityStruct>(array, index, new EGID(entityID, group));
} }
public EGID MatchBlockOutputToPort(Block block, byte portUsage, out bool exists)
{
var ports = entitiesDB.QueryEntityOptional<BlockPortsStruct>(block);
exists = ports;
return new EGID(ports.Get().firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group);
}
public EGID MatchBlockOutputToPort(EGID block, byte portUsage, out bool exists)
{
if (!entitiesDB.Exists<BlockPortsStruct>(block))
{
exists = false;
return default; return default;
} }
exists = true;
public OptionalRef<WireEntityStruct> MatchPortToWire(PortEntityStruct port, EGID blockID, out EGID wireID) BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
{ return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group);
var (wires, ids, count) = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group);
for (uint i = 0; i < count; i++)
{
if ((wires[i].destinationPortUsage == port.usage && wires[i].destinationBlockEGID == blockID)
|| (wires[i].sourcePortUsage == port.usage && wires[i].sourceBlockEGID == blockID))
{
wireID = new EGID(ids[i], BuildModeWiresGroups.WiresGroup.Group);
return new OptionalRef<WireEntityStruct>(wires, i);
}
} }
wireID = default; public ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists)
return default; {
ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID);
var wires = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<WiresGroup>.Group);
var wiresB = wires.ToBuffer().buffer;
for (uint i = 0; i < wires.count; i++)
{
if ((wiresB[i].destinationPortUsage == port.usage && wiresB[i].destinationBlockEGID == blockID)
|| (wiresB[i].sourcePortUsage == port.usage && wiresB[i].sourceBlockEGID == blockID))
{
exists = true;
return ref wiresB[i];
}
}
exists = false;
WireEntityStruct[] defRef = new WireEntityStruct[1];
return ref defRef[0];
} }
public EGID MatchBlocksToWire(EGID startBlock, EGID endBlock, byte startPort = byte.MaxValue, byte endPort = byte.MaxValue) public ref WireEntityStruct MatchBlocksToWire(EGID startBlock, EGID endBlock, out bool exists, byte startPort = byte.MaxValue,
byte endPort = byte.MaxValue)
{ {
EGID[] startPorts; 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)
{
wireEGID = wire.ID;
endPortEGID = signalEngine.MatchBlockInputToPort(end, wire.destinationPortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockOutputToPort(start, wire.sourcePortUsage, out exists);
if (!exists) throw new WireInvalidException("Wire start port not found");
inputToOutput = false;
endPort = wire.destinationPortUsage;
startPort = wire.sourcePortUsage;
}
else
{ {
// flip I/O around and try again // flip I/O around and try again
wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, endPort, startPort); wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, out exists, endPort, startPort);
flipped = true; 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. // 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 // This makes wire traversal easier, but makes logic in this class a bit more complex
} endPort = wire.sourcePortUsage;
startPort = wire.destinationPortUsage;
if (wire != default)
{
th.Construct(start.Id, end.Id, startPort, endPort, wire, flipped);
} }
else else
{ {
throw new WireInvalidException("Wire not found"); 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;
@ -122,7 +59,5 @@ namespace TechbloxModdingAPI
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,19 +1,21 @@
using RobocraftX.Common.Input; using System;
using TechbloxModdingAPI.App; using RobocraftX.Common;
using RobocraftX.Common.Input;
using Svelto.ECS;
using TechbloxModdingAPI.Utility; 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.
/// </summary> /// </summary>
/// <param name="input">The custom input.</param> /// <param name="input">The custom input.</param>
public static void CustomInput(LocalCosmeticInputEntityComponent input) public static void CustomInput(LocalInputEntityStruct input)
{ {
inputEngine.SendCustomInput(input); inputEngine.SendCustomInput(input);
} }
@ -32,7 +34,7 @@ namespace TechbloxModdingAPI.Input
inputEngine.SendCustomPlayerInput(input, playerID); inputEngine.SendCustomPlayerInput(input, playerID);
} }
public static LocalCosmeticInputEntityComponent GetInput() public static LocalInputEntityStruct GetInput()
{ {
return inputEngine.GetInput(); return inputEngine.GetInput();
} }
@ -51,18 +53,26 @@ namespace TechbloxModdingAPI.Input
/// Omit any parameter you do not want to affect. /// Omit any parameter you do not want to affect.
/// Parameters that end with "?" don't do anything... yet. /// Parameters that end with "?" don't do anything... yet.
/// </summary> /// </summary>
/// <param name="playerID">The player. Omit this to use the local player.</param>
/// <param name="hotbar">Select the hotbar slot by number.</param> /// <param name="hotbar">Select the hotbar slot by number.</param>
/// <param name="commandLine">Toggle the command line?</param> /// <param name="commandLine">Toggle the command line?</param>
/// <param name="escape">Open escape menu?</param> /// <param name="escape">Open escape menu?</param>
/// <param name="enter">Page return?</param> /// <param name="enter">Page return?</param>
/// <param name="debug">Toggle debug display?</param> /// <param name="debug">Toggle debug display?</param>
/// <param name="next">Select next?</param>
/// <param name="previous">Select previous?</param>
/// <param name="tab">Tab?</param>
/// <param name="colour">Toggle to hotbar colour mode?</param> /// <param name="colour">Toggle to hotbar colour mode?</param>
/// <param name="hotbarPage">Select the hotbar page by number?</param> /// <param name="hotbarPage">Select the hotbar page by number?</param>
/// <param name="quickSave">Quicksave?</param> /// <param name="quickSave">Quicksave?</param>
/// <param name="paste">Paste?</param> /// <param name="paste">Paste?</param>
public static void GuiInput(int hotbar = -1, bool escape = false, bool enter = false, bool debug = false, int hotbarPage = -1, bool quickSave = false, bool paste = false) public static void GuiInput(uint playerID = uint.MaxValue, int hotbar = -1, bool escape = false, bool enter = false, bool debug = false, bool next = false, bool previous = false, bool tab = false, int hotbarPage = -1, bool quickSave = false, bool paste = false)
{ {
ref LocalCosmeticInputEntityComponent currentInput = ref inputEngine.GetInputRef(); if (playerID == uint.MaxValue)
{
playerID = inputEngine.GetLocalPlayerID();
}
ref LocalInputEntityStruct currentInput = ref inputEngine.GetInputRef();
//Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}"); //Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}");
// set inputs // set inputs
switch(hotbar) switch(hotbar)
@ -82,6 +92,9 @@ namespace TechbloxModdingAPI.Input
if (escape) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.Escape; if (escape) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.Escape;
if (enter) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.Return; if (enter) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.Return;
if (debug) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.ToggleDebugDisplay; if (debug) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.ToggleDebugDisplay;
if (next) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.SelectNext;
if (previous) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.SelectPrev;
if (tab) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.Tab;
switch (hotbarPage) switch (hotbarPage)
{ {
case 1: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage1; break; case 1: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage1; break;
@ -94,46 +107,47 @@ namespace TechbloxModdingAPI.Input
case 8: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage8; break; case 8: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage8; break;
case 9: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage9; break; case 9: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage9; break;
case 10: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage10; break; case 10: currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.HotbarPage10; break;
default: break;
} }
//RewiredConsts.Action //RewiredConsts.Action
if (quickSave) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.QuickSave; if (quickSave) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.QuickSave;
if (paste) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.SelectLastCopiedBlueprint; if (paste) currentInput.guiMask |= RobocraftX.Common.Input.GuiInput.PasteSelection;
} }
public static void ActionInput(uint playerID = uint.MaxValue, bool toggleMode = false, bool forward = false, bool backward = false, bool up = false, bool down = false, bool left = false, bool right = false, bool sprint = false, bool toggleFly = false, bool alt = false, bool primary = false, bool secondary = false, bool tertiary = false, bool primaryHeld = false, bool secondaryHeld = false, bool toggleUnitGrid = false, bool ctrl = false, bool toggleColourMode = false, bool scaleBlockUp = false, bool scaleBlockDown = false, bool rotateBlockClockwise = false, bool rotateBlockCounterclockwise = false, bool cutSelection = false, bool copySelection = false, bool deleteSelection = false) 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;
@ -32,12 +30,12 @@ namespace TechbloxModdingAPI.Input
IsReady = true; IsReady = true;
} }
public bool SendCustomInput(LocalCosmeticInputEntityComponent input) public bool SendCustomInput(LocalInputEntityStruct input)
{ {
EGID egid = CommonExclusiveGroups.GameStateEGID; EGID egid = CommonExclusiveGroups.GameStateEGID;
if (entitiesDB.Exists<LocalCosmeticInputEntityComponent>(egid)) if (entitiesDB.Exists<LocalInputEntityStruct>(egid))
{ {
ref LocalCosmeticInputEntityComponent ies = ref entitiesDB.QueryEntity<LocalCosmeticInputEntityComponent>(egid); ref LocalInputEntityStruct ies = ref entitiesDB.QueryEntity<LocalInputEntityStruct>(egid);
ies = input; ies = input;
return true; return true;
} }
@ -56,14 +54,14 @@ namespace TechbloxModdingAPI.Input
else return false; else return false;
} }
public LocalCosmeticInputEntityComponent GetInput() public LocalInputEntityStruct GetInput()
{ {
EGID egid = CommonExclusiveGroups.GameStateEGID; EGID egid = CommonExclusiveGroups.GameStateEGID;
if (entitiesDB.Exists<LocalCosmeticInputEntityComponent>(egid)) if (entitiesDB.Exists<LocalInputEntityStruct>(egid))
{ {
return entitiesDB.QueryEntity<LocalCosmeticInputEntityComponent>(egid); return entitiesDB.QueryEntity<LocalInputEntityStruct>(egid);
} }
else return default(LocalCosmeticInputEntityComponent); else return default(LocalInputEntityStruct);
} }
public LocalPlayerInputEntityStruct GetPlayerInput(uint playerID, bool remote = false) public LocalPlayerInputEntityStruct GetPlayerInput(uint playerID, bool remote = false)
@ -76,10 +74,10 @@ namespace TechbloxModdingAPI.Input
else return default; else return default;
} }
public ref LocalCosmeticInputEntityComponent GetInputRef() public ref LocalInputEntityStruct GetInputRef()
{ {
EGID egid = CommonExclusiveGroups.GameStateEGID; EGID egid = CommonExclusiveGroups.GameStateEGID;
return ref entitiesDB.QueryEntity<LocalCosmeticInputEntityComponent>(egid); return ref entitiesDB.QueryEntity<LocalInputEntityStruct>(egid);
} }
public ref LocalPlayerInputEntityStruct GetPlayerInputRef(uint playerID, bool remote = false) public ref LocalPlayerInputEntityStruct GetPlayerInputRef(uint playerID, bool remote = 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,12 +20,12 @@ 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;*/
GUIStyle guiStyle = Constants.Default.box; GUIStyle guiStyle = Constants.Default.box;
UIElement[] elems = elements.ToArrayFast(out int count); UIElement[] elems = elements.ToArrayFast(out uint count);
if (automaticLayout) if (automaticLayout)
{ {
GUILayout.BeginArea(Box, guiStyle); GUILayout.BeginArea(Box, guiStyle);
@ -61,6 +61,16 @@ namespace TechbloxModdingAPI.Interface.IMGUI
} }
} }
/// <summary>
/// The group's unique name.
/// </summary>
public string Name { get; }
/// <summary>
/// Whether to display the group and everything in it.
/// </summary>
public bool Enabled { set; get; } = true;
/// <summary> /// <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>
@ -113,7 +132,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
{ {
if (index < 0 || index >= elements.count) return false; if (index < 0 || index >= elements.count) return false;
IMGUIManager.AddElement(elements[index]); // re-add to global manager IMGUIManager.AddElement(elements[index]); // re-add to global manager
elements.RemoveAt((uint) index); elements.RemoveAt(index);
return true; return true;
} }
@ -124,7 +143,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// <returns>The element's index, or -1 if not found.</returns> /// <returns>The element's index, or -1 if not found.</returns>
public int IndexOf(UIElement element) public int IndexOf(UIElement element)
{ {
UIElement[] elems = elements.ToArrayFast(out int count); UIElement[] elems = elements.ToArrayFast(out uint count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
if (elems[i].Name == element.Name) if (elems[i].Name == element.Name)

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,13 +51,15 @@ 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 // init utility
Logging.MetaDebugLog($"Initializing Utility"); Logging.MetaDebugLog($"Initializing Utility");
Utility.GameState.Init(); Utility.GameState.Init();
@ -68,24 +72,14 @@ namespace TechbloxModdingAPI
Block.Init(); Block.Init();
BlockGroup.Init(); BlockGroup.Init();
Wire.Init(); Wire.Init();
// init client
Logging.MetaDebugLog($"Initializing Client"); Logging.MetaDebugLog($"Initializing Client");
Client.Init(); Client.Init();
Game.Init(); Game.Init();
// init UI // init UI
Logging.MetaDebugLog($"Initializing UI");
Interface.IMGUI.Constants.Init(); Interface.IMGUI.Constants.Init();
Interface.IMGUI.IMGUIManager.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"); 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>
/// Shuts down & cleans up the TechbloxModdingAPI. /// Shuts down & cleans up the TechbloxModdingAPI.
@ -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

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

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,8 +25,8 @@ 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 uint buffLen), serializationData.dataPos);
uint originalPos = serializationData.dataPos; uint originalPos = serializationData.dataPos;
Logging.MetaDebugLog($"dataPos: {originalPos}"); Logging.MetaDebugLog($"dataPos: {originalPos}");
// Add frame start so it's easier to find TechbloxModdingAPI-serialized components // Add frame start so it's easier to find TechbloxModdingAPI-serialized components
@ -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}");
@ -74,7 +74,7 @@ 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

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

View file

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

View file

@ -1,16 +1,13 @@
using System; using 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.Camera; using Techblox.Camera;
using Techblox.Character; using Techblox.FlyCam;
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:
id = playerEngine.GetLocalPlayer(); this.Id = playerEngine.GetLocalPlayer();
break; break;
case PlayerType.Remote: case PlayerType.Remote:
id = playerEngine.GetRemotePlayer(); this.Id = playerEngine.GetRemotePlayer();
break;
default:
id = uint.MaxValue;
break; break;
} }
if (this.Id == uint.MaxValue)
if (id == uint.MaxValue)
{ {
throw new PlayerNotFoundException($"No player of {player} type exists"); throw new PlayerNotFoundException($"No player of {player} type exists");
} }
return new EGID(id, CharacterExclusiveGroups.OnFootGroup);
})
{
this.Type = player; 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.
@ -164,10 +141,10 @@ namespace TechbloxModdingAPI
public float3 Rotation public float3 Rotation
{ {
get => ((Quaternion) (GameState.IsBuildMode() get => ((Quaternion) (GameState.IsBuildMode()
? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation ? playerEngine.GetCameraStruct<CharacterCameraEntityStruct>(Id).Get().rotation
: playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation)).eulerAngles; : playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation)).eulerAngles;
set => _ = GameState.IsBuildMode() set => _ = GameState.IsBuildMode()
? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation = quaternion.Euler(value) ? playerEngine.GetCameraStruct<CharacterCameraEntityStruct>(Id).Get().rotation = quaternion.Euler(value)
: playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation = quaternion.Euler(value); : playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation = quaternion.Euler(value);
} }
@ -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<PlayerInputTimeStoppedContextStruct>(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.
@ -377,10 +357,10 @@ namespace TechbloxModdingAPI
public bool Sprinting public bool Sprinting
{ {
get => GameState.IsBuildMode() get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting ? playerEngine.GetCharacterStruct<FlyCamMovementComponent>(Id).Get().sprinting
: playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting; : playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting;
set => _ = GameState.IsBuildMode() set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting = value ? playerEngine.GetCharacterStruct<FlyCamMovementComponent>(Id).Get().sprinting = value
: playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting = value; : playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting = value;
} }
@ -390,10 +370,10 @@ namespace TechbloxModdingAPI
public float SpeedSetting public float SpeedSetting
{ {
get => GameState.IsBuildMode() get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed ? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().speed
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed;
set => _ = GameState.IsBuildMode() set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed = value ? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().speed = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed = value; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed = value;
} }
@ -403,10 +383,10 @@ namespace TechbloxModdingAPI
public float SpeedSprintMultiplierSetting public float SpeedSprintMultiplierSetting
{ {
get => GameState.IsBuildMode() get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier ? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().speedSprintMultiplier
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier;
set => _ = GameState.IsBuildMode() set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier = value ? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().speedSprintMultiplier = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier = value; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier = value;
} }
@ -416,10 +396,10 @@ namespace TechbloxModdingAPI
public float AccelerationSetting public float AccelerationSetting
{ {
get => GameState.IsBuildMode() get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration ? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().acceleration
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration;
set => _ = GameState.IsBuildMode() set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration = value ? playerEngine.GetCharacterStruct<FlyCamMovementSettingsComponent>(Id).Get().acceleration = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration = value; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration = value;
} }
@ -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,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using RobocraftX.Character; using RobocraftX.Character;
@ -8,24 +10,22 @@ 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 Techblox.FlyCam;
using Unity.Mathematics; using Unity.Mathematics;
using Unity.Physics;
using UnityEngine;
using HarmonyLib;
using RobocraftX.Common;
using Svelto.ECS.DataStructures; using Svelto.ECS.DataStructures;
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 +33,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 +50,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 +61,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 +107,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
@ -130,7 +140,7 @@ namespace TechbloxModdingAPI.Players
{ {
group = default; group = default;
if (GameState.IsBuildMode()) if (GameState.IsBuildMode())
return entitiesDB.QueryEntityOptional<T>(new EGID(playerId, LocalBuildingDrone.BuildGroup)); return entitiesDB.QueryEntityOptional<T>(new EGID(playerId, Techblox.FlyCam.FlyCam.Group));
var characterGroups = CharacterExclusiveGroups.AllCharacters; var characterGroups = CharacterExclusiveGroups.AllCharacters;
for (int i = 0; i < characterGroups.count; i++) for (int i = 0; i < characterGroups.count; i++)
@ -157,35 +167,34 @@ namespace TechbloxModdingAPI.Players
public OptionalRef<T> GetCameraStruct<T>(uint playerId) where T : unmanaged, IEntityComponent public OptionalRef<T> GetCameraStruct<T>(uint playerId) where T : unmanaged, IEntityComponent
{ {
return entitiesDB.QueryEntityOptional<T>(new EGID(playerId, CameraExclusiveGroups.PhysicCameraGroup)); return entitiesDB.QueryEntityOptional<T>(new EGID(playerId, CameraExclusiveGroups.CameraGroup));
} }
public EGID GetThingLookedAt(uint playerId, float maxDistance = -1f) public EGID GetThingLookedAt(uint playerId, float maxDistance = -1f)
{ {
var opt = GetCameraStruct<PhysicCameraRayCastEntityStruct>(playerId); var opt = GetCameraStruct<CharacterCameraRayCastEntityStruct>(playerId);
if (!opt) return default; if (!opt) return EGID.Empty;
PhysicCameraRayCastEntityStruct rayCast = opt; CharacterCameraRayCastEntityStruct 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 +205,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,33 +1,64 @@
using System; using System;
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 TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Commands; using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Interface.IMGUI;
using TechbloxModdingAPI.Players;
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}");
};
private static Harmony harmony { get; set; }
public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
public string HarmonyID { get; } = "org.git.exmods.modtainers.techbloxmoddingapi";
public override void OnApplicationQuit()
{
Main.Shutdown();
}
public override void OnApplicationStart()
{
FileLog.Reset();
Harmony.DEBUG = true;
Main.Init();
Logging.MetaDebugLog($"Version group id {(uint)ApiExclusiveGroups.versionGroup}");
// disable background music
Logging.MetaDebugLog("Audio Mixers: " + string.Join(",", AudioTools.GetMixers()));
//AudioTools.SetVolume(0.0f, "Music"); // The game now sets this from settings again after this is called :(
//Utility.VersionTracking.Enable();//(very) unstable
// debug/test handlers
Client.EnterMenu += (sender, args) => throw new Exception("Test handler always throws an exception!");
// debug/test commands
if (Dependency.Hell("ExtraCommands"))
{
CommandBuilder.Builder() CommandBuilder.Builder()
.Name("Exit") .Name("Exit")
.Description("Close Techblox immediately, without any prompts") .Description("Close Techblox immediately, without any prompts")
@ -40,44 +71,218 @@ namespace TechbloxModdingAPI.Tests
.Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; }) .Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
.Build(); .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); Game.AddPersistentDebugInfo("InstalledMods", InstalledMods);
Block.Placed += (sender, args) =>
Logging.MetaDebugLog("Placed block " + args.Block);
Block.Removed += (sender, args) =>
Logging.MetaDebugLog("Removed block " + args.Block);
}
// Plugin startup logic // dependency test
Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!"); if (Dependency.Hell("TechbloxScripting", new Version("0.0.1.0")))
{
Logging.LogWarning("You're in TechbloxScripting dependency hell");
}
else
{
Logging.Log("Compatible TechbloxScripting detected");
}
// Interface test
/*Group uiGroup = new Group(new Rect(20, 20, 200, 500), "TechbloxModdingAPI_UITestGroup", true);
var button = new Button("TEST");
button.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
var button2 = new Button("TEST2");
button2.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
Text uiText = new Text("<Input!>", multiline: true);
uiText.OnEdit += (t, txt) => { Logging.MetaDebugLog($"Text in {((Text)t).Name} is now '{txt}'"); };
Label uiLabel = new Label("Label!");
Image uiImg = new Image(name:"Behold this texture!");
uiImg.Enabled = false;
uiGroup.AddElement(button);
uiGroup.AddElement(button2);
uiGroup.AddElement(uiText);
uiGroup.AddElement(uiLabel);
uiGroup.AddElement(uiImg);*/
/*Addressables.LoadAssetAsync<Texture2D>("Assets/Art/Textures/UI/FrontEndMap/RCX_Blue_Background_5k.jpg")
.Completed +=
handle =>
{
uiImg.Texture = handle.Result;
uiImg.Enabled = true;
Logging.MetaDebugLog($"Got blue bg asset {handle.Result}");
};*/
/*((FasterList<GuiInputMap.GuiInputMapElement>)AccessTools.Property(typeof(GuiInputMap), "GuiInputsButtonDown").GetValue(null))
.Add(new GuiInputMap.GuiInputMapElement(RewiredConsts.Action.ToggleCommandLine, GuiIn))*/
#if TEST #if TEST
TestRoot.RunTests(); TestRoot.RunTests();
#endif #endif
} }
private void OnDestroy()
{
Main.Shutdown();
}
private string modsString; 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();
} }
public override void OnUpdate()
{
if (UnityEngine.Input.GetKeyDown(KeyCode.End))
{
Console.WriteLine("Pressed button to toggle console");
FakeInput.CustomInput(new LocalInputEntityStruct {commandLineToggleInput = true});
}
} }
[HarmonyPatch] [HarmonyPatch]
public class MinimumSpecsPatch public class MinimumSpecsPatch
{ {
public static bool Prefix(ref bool __result) public static bool Prefix()
{ {
__result = true;
return false; return false;
} }
public static MethodInfo TargetMethod() public static MethodInfo TargetMethod()
{ {
return ((Func<bool>) MinimumSpecsCheck.CheckRequirementsMet).Method; 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>
@ -63,40 +58,22 @@ namespace TechbloxModdingAPI.Utility
/// <returns>The value or the default value</returns> /// <returns>The value or the default value</returns>
public ref T Get() public ref T Get()
{ {
CompRefCache.Default = default; //The default value can be changed by mods
if (state == State.Empty) return ref CompRefCache.Default; if (state == State.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>
private struct CompRefCache internal struct CompRefCache
{ {
public static T Default; public static T Default;
} }

View file

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

View file

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

View file

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

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