Compare commits

..

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

83 changed files with 2712 additions and 5481 deletions

View file

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

View file

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

View file

@ -8,6 +8,7 @@ using System.Reflection;
using Gamecraft.Tweaks; using Gamecraft.Tweaks;
using RobocraftX.Common; using RobocraftX.Common;
using Svelto.ECS; using Svelto.ECS;
using Techblox.EngineBlock;
namespace CodeGenerator namespace CodeGenerator
{ {
@ -22,7 +23,7 @@ namespace CodeGenerator
group = GetGroup(name) + "_BLOCK_BUILD_GROUP"; group = GetGroup(name) + "_BLOCK_BUILD_GROUP";
} }
if (!group.Contains('.')) if (!group.Contains("."))
group = "CommonExclusiveGroups." + group; group = "CommonExclusiveGroups." + group;
var codeUnit = new CodeCompileUnit(); var codeUnit = new CodeCompileUnit();
@ -67,7 +68,7 @@ namespace CodeGenerator
codeUnit.Namespaces.Add(ns); codeUnit.Namespaces.Add(ns);
var provider = CodeDomProvider.CreateProvider("CSharp"); var provider = CodeDomProvider.CreateProvider("CSharp");
var path = $@"../../../../TechbloxModdingAPI/Blocks/{name}.cs"; var path = $@"..\..\..\TechbloxModdingAPI\Blocks\{name}.cs";
using (var sw = new StreamWriter(path)) using (var sw = new StreamWriter(path))
{ {
provider.GenerateCodeFromCompileUnit(codeUnit, sw, new CodeGeneratorOptions {BracingStyle = "C"}); provider.GenerateCodeFromCompileUnit(codeUnit, sw, new CodeGeneratorOptions {BracingStyle = "C"});
@ -96,8 +97,6 @@ namespace CodeGenerator
{ {
if (!typeof(IEntityComponent).IsAssignableFrom(type)) if (!typeof(IEntityComponent).IsAssignableFrom(type))
throw new ArgumentException("Type must be an entity component"); 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()) foreach (var field in type.GetFields())
{ {
var attr = field.GetCustomAttribute<TweakableStatAttribute>(); var attr = field.GetCustomAttribute<TweakableStatAttribute>();
@ -109,20 +108,10 @@ namespace CodeGenerator
} }
propName = char.ToUpper(propName[0]) + propName.Substring(1); propName = char.ToUpper(propName[0]) + propName.Substring(1);
var getStruct = new CodeMethodInvokeExpression( var structFieldReference = new CodeFieldReferenceExpression(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"), new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"),
"GetBlockInfo"), "GetBlockInfo", new CodeTypeReference(type)),
new CodeThisReferenceExpression(), reflectedType, new CodePrimitiveExpression(field.Name))); new CodeThisReferenceExpression()), 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 cl.Members.Add(new CodeMemberProperty
{ {
Name = propName, Name = propName,
@ -130,23 +119,18 @@ namespace CodeGenerator
HasSet = true, HasSet = true,
GetStatements = GetStatements =
{ {
new CodeMethodReturnStatement(reflection ? reflectedGet : structFieldReference) new CodeMethodReturnStatement(structFieldReference)
}, },
SetStatements = SetStatements =
{ {
reflection new CodeAssignStatement(structFieldReference, new CodePropertySetValueReferenceExpression())
? (CodeStatement)new CodeExpressionStatement(reflectedSet)
: new CodeAssignStatement(structFieldReference, new CodePropertySetValueReferenceExpression())
}, },
Type = new CodeTypeReference(field.FieldType), Type = new CodeTypeReference(field.FieldType),
Attributes = MemberAttributes.Public | MemberAttributes.Final, Attributes = MemberAttributes.Public | MemberAttributes.Final,
Comments = Comments =
{ {
_start, _start, new CodeCommentStatement($"Gets or sets the {baseClass}'s {propName} property." +
new CodeCommentStatement($"Gets or sets the {baseClass}'s {propName} property." + $" {(attr != null ? "Tweakable stat." : "May not be saved.")}", true), _end
$" {(attr != null ? "Tweakable stat." : "May not be saved.")}",
true),
_end
} }
}); });
} }

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using HarmonyLib;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.GroupTags;
using RobocraftX.PilotSeat; using RobocraftX.PilotSeat;
using Svelto.ECS;
using Techblox.EngineBlock; using Techblox.EngineBlock;
using Techblox.ServoBlocksServer;
using Techblox.WheelRigBlock; using Techblox.WheelRigBlock;
namespace CodeGenerator namespace CodeGenerator
@ -14,17 +9,12 @@ namespace CodeGenerator
internal class Program internal class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)
{
GenerateBlockClasses();
}
private static void GenerateBlockClasses()
{ {
var bcg = new BlockClassGenerator(); var bcg = new BlockClassGenerator();
bcg.Generate("Engine", null, new Dictionary<string, string> bcg.Generate("Engine", null, new Dictionary<string, string>
{ {
{ "engineOn", "On" } { "engineOn", "On" }
}, AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), // Simulation time properties }, typeof(EngineBlockComponent), // Simulation time properties
typeof(EngineBlockTweakableComponent), typeof(EngineBlockReadonlyComponent)); typeof(EngineBlockTweakableComponent), typeof(EngineBlockReadonlyComponent));
bcg.Generate("DampedSpring", "DAMPEDSPRING_BLOCK_GROUP", new Dictionary<string, string> bcg.Generate("DampedSpring", "DAMPEDSPRING_BLOCK_GROUP", new Dictionary<string, string>
{ {
@ -32,7 +22,7 @@ namespace CodeGenerator
}, },
typeof(TweakableJointDampingComponent), typeof(DampedSpringReadOnlyStruct)); typeof(TweakableJointDampingComponent), typeof(DampedSpringReadOnlyStruct));
bcg.Generate("LogicGate", "LOGIC_BLOCK_GROUP"); bcg.Generate("LogicGate", "LOGIC_BLOCK_GROUP");
bcg.Generate("Servo", types: typeof(ServoReadOnlyTweakableComponent), renames: new Dictionary<string, string> bcg.Generate("Servo", types: typeof(ServoReadOnlyStruct), renames: new Dictionary<string, string>
{ {
{"minDeviation", "MinimumAngle"}, {"minDeviation", "MinimumAngle"},
{"maxDeviation", "MaximumAngle"}, {"maxDeviation", "MaximumAngle"},
@ -47,7 +37,6 @@ namespace CodeGenerator
{"pistonVelocity", "MaximumForce"} {"pistonVelocity", "MaximumForce"}
}, typeof(PistonReadOnlyStruct)); }, typeof(PistonReadOnlyStruct));
bcg.Generate("Motor", null, null, typeof(MotorReadOnlyStruct)); bcg.Generate("Motor", null, null, typeof(MotorReadOnlyStruct));
//bcg.Generate("ObjectID", "ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP", null, typeof(ObjectIDTweakableComponent));
} }
} }
} }

View file

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("CodeGenerator")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CodeGenerator")]
[assembly: AssemblyCopyright("Copyright © ExMods 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("0EBB6400-95A7-4A3D-B2ED-BF31E364CC10")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

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

@ -7,8 +7,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechbloxModdingAPI", "Techb
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGenerator", "CodeGenerator\CodeGenerator.csproj", "{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGenerator", "CodeGenerator\CodeGenerator.csproj", "{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}"
EndProject 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
@ -28,12 +26,6 @@ Global
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Test|Any CPU.Build.0 = 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,44 @@
using System;
using RobocraftX.GUI.MyGamesScreen;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App
{
public class AppEngine : IFactoryEngine
{
public WrappedHandler<MenuEventArgs> EnterMenu;
public WrappedHandler<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;
ExitMenu.Invoke(this, new MenuEventArgs { });
}
public void Ready()
{
IsInMenu = true;
EnterMenu.Invoke(this, new MenuEventArgs { });
}
// app functionality
public bool IsInMenu
{
get;
private set;
} = false;
}
}

View file

@ -14,12 +14,12 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public class Client public class Client
{ {
public static Client Instance { get; } = new Client();
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>
@ -93,26 +93,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 +113,9 @@ 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]);*/
} }
// Creating delegates once is faster than reflection every time // Creating delegates once is faster than reflection every time
@ -149,23 +140,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;
@ -316,7 +318,7 @@ namespace TechbloxModdingAPI.App
get get
{ {
if (menuMode || !VerifyMode()) return CurrentGameMode.None; if (menuMode || !VerifyMode()) return CurrentGameMode.None;
return gameEngine.GetGameData().gameMode == GameMode.CreateWorld ? CurrentGameMode.Build : CurrentGameMode.Play; return (CurrentGameMode) GameMode.CurrentMode;
} }
} }

View file

@ -1,7 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using HarmonyLib;
using RobocraftX;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.Schedulers; using RobocraftX.Schedulers;
using RobocraftX.SimulationModeState; using RobocraftX.SimulationModeState;
@ -10,15 +8,11 @@ using Svelto.Tasks;
using Svelto.Tasks.Lean; using Svelto.Tasks.Lean;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using RobocraftX.Common.Loading; using RobocraftX.Common.Loading;
using RobocraftX.Multiplayer;
using RobocraftX.ScreenshotTaker; using RobocraftX.ScreenshotTaker;
using Techblox.Environment.Transition; using Techblox.Environment.Transition;
using Techblox.GameSelection; 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
@ -36,34 +30,16 @@ namespace TechbloxModdingAPI.App
public EntitiesDB entitiesDB { set; private get; } public EntitiesDB entitiesDB { set; private get; }
private bool enteredGame; private bool enteredGame;
private bool loadingFinished;
private bool playerJoined;
public void Dispose() public void Dispose()
{ {
if (GameReloadedPatch.IsReload)
return; // Toggling time mode
ExitGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID }); 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)
return; // Toggling time mode
enteredGame = 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 +54,7 @@ namespace TechbloxModdingAPI.App
{ {
if (async) if (async)
{ {
ExitCurrentGameAsync().RunOn(ClientLean.EveryFrameStepRunner_TimeRunningAndStopped); ExitCurrentGameAsync().RunOn(Lean.EveryFrameStepRunner_TimeRunningAndStopped);
} }
else else
{ {
@ -118,41 +94,27 @@ namespace TechbloxModdingAPI.App
public void ToggleTimeMode() public void ToggleTimeMode()
{ {
if (TimeRunningModeUtil.IsTimeStoppedMode(entitiesDB)) if (!entitiesDB.FoundInGroups<BlockTagEntityStruct>())
FakeInput.ActionInput(toggleMode: true); throw new AppStateException("At least one block must exist in the world to enter simulation");
else SwitchAnimationUtil.Start(entitiesDB);
{ }
IEnumerator<TaskContract> ReloadBuildModeTask()
{
SwitchAnimationUtil.Start(entitiesDB);
while (SwitchAnimationUtil.IsFadeOutActive(entitiesDB))
yield return (TaskContract)Yield.It;
FullGameFields._multiplayerParams.MultiplayerMode = MultiplayerMode.SinglePlayer;
AccessTools.Method(typeof(FullGameCompositionRoot), "ReloadGame")
.Invoke(FullGameFields.Instance, new object[] { });
}
ReloadBuildModeTask().RunOn(ClientLean.UIScheduler);
}
}
public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid) 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) foreach (var (blocks, _) in allBlocks)
{ {
var (buffer, count) = blocks.ToBuffer();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
var id = new EGID(ids[i], group);
uint dbid; uint dbid;
if (filter == BlockIDs.Invalid) if (filter == BlockIDs.Invalid)
dbid = (uint)filter; dbid = (uint)filter;
else else
dbid = entitiesDB.QueryEntity<DBEntityStruct>(id).DBID; dbid = entitiesDB.QueryEntity<DBEntityStruct>(buffer[i].ID).DBID;
var ownership = entitiesDB.QueryEntity<BlockOwnershipComponent>(id).BlockOwnership; if (dbid == (ulong)filter)
if ((ownership & BlockOwnership.User) != 0 && dbid == (ulong)filter) blockEGIDs.Add(buffer[i].ID);
blockEGIDs.Add(id);
} }
} }
@ -180,16 +142,9 @@ namespace TechbloxModdingAPI.App
public void Remove(ref LoadingActionEntityStruct entityComponent, EGID egid) public void Remove(ref LoadingActionEntityStruct entityComponent, EGID egid)
{ // Finished loading { // Finished loading
if (!enteredGame) return; 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 }); EnterGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID });
IsInGame = true; IsInGame = true;
enteredGame = false;
} }
} }
} }

View file

@ -3,15 +3,16 @@ using System.Reflection;
using HarmonyLib; using HarmonyLib;
using RobocraftX; using RobocraftX;
using RobocraftX.Common;
using RobocraftX.FrontEnd;
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 Techblox.GameSelection;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using GameMode = RobocraftX.Common.GameMode;
namespace TechbloxModdingAPI.App namespace TechbloxModdingAPI.App
{ {
@ -55,12 +56,13 @@ namespace TechbloxModdingAPI.App
public Game[] GetMyGames() public Game[] GetMyGames()
{ {
var (mgsevs, count) = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames); EntityCollection<MyGameDataEntityStruct> mgsevs = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
Game[] games = new Game[count]; var mgsevsB = mgsevs.ToBuffer().buffer;
for (int i = 0; i < count; i++) Game[] games = new Game[mgsevs.count];
for (int i = 0; i < mgsevs.count; i++)
{ {
Utility.Logging.MetaDebugLog($"Found game named {mgsevs[i].GameName}"); Utility.Logging.MetaDebugLog($"Found game named {mgsevsB[i].GameName}");
games[i] = new Game(mgsevs[i].ID); games[i] = new Game(mgsevsB[i].ID);
} }
return games; return games;
} }
@ -83,13 +85,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;
@ -102,16 +105,20 @@ namespace TechbloxModdingAPI.App
return EnterGame(mgdes.GameName, mgdes.FileId); return EnterGame(mgdes.GameName, mgdes.FileId);
} }
public bool EnterGame(ECSString gameName, string fileId, bool autoEnterSim = false) public bool EnterGame(string gameName, string fileId, bool autoEnterSim = false)
{ {
FullGameFields._multiplayerParams.MultiplayerMode = MultiplayerMode.SinglePlayer; GameMode.CurrentMode = autoEnterSim ? RCXMode.Play : RCXMode.Build;
ref var selection = ref entitiesDB.QueryEntity<GameSelectionComponent>(GameSelectionConstants.GameSelectionEGID); var data = new GameSelectionData
selection.userContentID.Set(fileId); {
selection.triggerStart = true; gameMode = Techblox.GameSelection.GameMode.PlayGame,
selection.saveType = SaveType.ExistingSave; gameType = GameType.MachineEditor,
selection.saveName = gameName; saveName = gameName,
selection.gameMode = GameMode.PlayGame; saveType = SaveType.ExistingSave,
selection.gameID.Set("GAMEID_Road_Track"); //TODO: Expose to the API gameID = "GAMEID_Road_Track", //TODO: Expose to the API
userContentID = fileId
};
// the private FullGameCompositionRoot.SwitchToGame() method gets passed to menu items for this reason
AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame").Invoke(FullGameFields.Instance, new object[]{data});
return true; return true;
} }
@ -171,7 +178,7 @@ namespace TechbloxModdingAPI.App
public static MethodBase TargetMethod() public static MethodBase TargetMethod()
{ {
return AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToMenu"); return AccessTools.Method(typeof(FullGameCompositionRoot), "GoToMenu");
} }
} }

View file

@ -8,10 +8,10 @@ using Svelto.ECS.EntityStructs;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using Unity.Mathematics; using Unity.Mathematics;
using Gamecraft.Blocks.GUI;
using HarmonyLib; using HarmonyLib;
using RobocraftX.PilotSeat; using RobocraftX.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,7 +67,7 @@ namespace TechbloxModdingAPI
/// <returns>The block object or null if doesn't exist</returns> /// <returns>The block object or null if doesn't exist</returns>
public static Block GetLastPlacedBlock() public static Block GetLastPlacedBlock()
{ {
uint lastBlockID = CommonExclusiveGroups.blockIDGeneratorClient.Peek() - 1; uint lastBlockID = (uint) AccessTools.Field(typeof(CommonExclusiveGroups), "_nextBlockEntityID").GetValue(null) - 1;
EGID? egid = BlockEngine.FindBlockEGID(lastBlockID); EGID? egid = BlockEngine.FindBlockEGID(lastBlockID);
return egid.HasValue ? New(egid.Value) : null; return egid.HasValue ? New(egid.Value) : null;
} }
@ -288,7 +288,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);
} }
} }
@ -340,15 +339,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 +361,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
{ {
@ -399,23 +390,6 @@ namespace TechbloxModdingAPI
set => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic = value; set => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic = value;
} }
/// <summary>
/// The mass of the block.
/// </summary>
public float Mass
{
get => BlockEngine.GetBlockInfo<MassStruct>(this).mass;
}
/// <summary>
/// Block complexity used for build rules. Determines the 'cost' of the block.
/// </summary>
public BlockComplexity Complexity
{
get => new(BlockEngine.GetBlockInfo<BlockComplexityComponent>(this));
set => BlockEngine.GetBlockInfo<BlockComplexityComponent>(this) = value;
}
/// <summary> /// <summary>
/// Whether the block exists. The other properties will return a default value if the block doesn't exist. /// Whether the block exists. The other properties will return a default value if the block doesn't exist.
/// If the block was just placed, then this will also return false but the properties will work correctly. /// If the block was just placed, then this will also return false but the properties will work correctly.
@ -441,10 +415,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>

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;

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

@ -273,111 +273,6 @@ namespace TechbloxModdingAPI.Blocks
/// <summary> /// <summary>
/// The grid block used by the world editor, named Small Grid like the other one /// The grid block used by the world editor, named Small Grid like the other one
/// </summary> /// </summary>
SmallGridInWorldEditor, 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

@ -31,16 +31,5 @@ namespace TechbloxModdingAPI.Blocks
SteelBodyworkPaintedChipped, SteelBodyworkPaintedChipped,
WoodPainted, WoodPainted,
WoodRoughGrungy, WoodRoughGrungy,
Boundary,
Emissive,
AircraftPanelingRivetedPainted,
AircraftPanelingRivetedMetallic,
SteelBodyworkPearlescent,
SteelBodyworkRadWrap,
SteelBodyworkGlitter,
BouncyRubber,
BouncyRubberTieDye,
BrickPainted,
FuturisticPanelingRivetedPainted,
} }
} }

View file

@ -5,8 +5,11 @@ using System.Reflection;
using DataLoader; using DataLoader;
using Svelto.Tasks; using Svelto.Tasks;
using Svelto.Tasks.Enumerators;
using Unity.Mathematics; using Unity.Mathematics;
using UnityEngine;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -79,50 +82,42 @@ namespace TechbloxModdingAPI.Blocks
yield break; 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())
{ {
if (property.Name == "Material" || property.Name == "Flipped") continue; // TODO: Crashes in game
if (property.Name == "Material" || property.Name == "Flipped")
{
Console.WriteLine("Block type: "+block.Type);
Console.WriteLine("Will set " + property.Name);
yield return new WaitForSecondsEnumerator(1).Continue();
}
//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 +126,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)
@ -159,7 +154,7 @@ namespace TechbloxModdingAPI.Blocks
continue; continue;
} }
var attr = property.GetCustomAttribute<TestValueAttribute>(); var attr = property.GetCustomAttribute<TestValueAttribute>();
if (!predicateToUse((got, valueToUse)) && (attr == null || !Equals(attr.PossibleValue, got))) if (!predicateToUse(got) && (attr == null || !Equals(attr.PossibleValue, got)))
{ {
Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}."); Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}.");
yield break; yield break;

View file

@ -23,7 +23,7 @@ namespace TechbloxModdingAPI.Blocks
{ {
} }
/*/// <summary> - TODO: Internal struct access /// <summary>
/// Gets or sets the Engine's On property. May not be saved. /// Gets or sets the Engine's On property. May not be saved.
/// </summary> /// </summary>
public bool On public bool On
@ -377,6 +377,6 @@ namespace TechbloxModdingAPI.Blocks
{ {
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime = value; 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,10 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using HarmonyLib; 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 +14,12 @@ 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.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 +44,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 +70,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 +79,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,25 +92,6 @@ 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;
@ -126,7 +101,6 @@ namespace TechbloxModdingAPI.Blocks.Engines
var skew = entitiesDB.QueryEntity<SkewComponent>(id); var skew = entitiesDB.QueryEntity<SkewComponent>(id);
entitiesDB.QueryEntity<RenderingDataStruct>(id).matrix = entitiesDB.QueryEntity<RenderingDataStruct>(id).matrix =
math.mul(float4x4.TRS(pos.position, rot.rotation, scale.scale), skew.skewMatrix); 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 +111,57 @@ 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.PublishEntityChangeDelayed<CubeMaterialStruct>(block.Id); entitiesDB.PublishEntityChange<CubeMaterialStruct>(block.Id);
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(block.Id); entitiesDB.PublishEntityChange<GFXPrefabEntityStructGPUI>(block.Id);
ref BuildingActionComponent local = ref BuildingActionComponent local =
ref entitiesDB.QueryEntity<BuildingActionComponent>(BuildingDroneUtility ref entitiesDB.QueryEntity<BuildingActionComponent>(BuildingDroneUtility
.GetLocalBuildingDrone(entitiesDB).ToEGID(entitiesDB)); .GetLocalBuildingDrone(entitiesDB).ToEGID(entitiesDB));
local.buildAction = BuildAction.ChangeMaterial; local.buildAction = BuildAction.ChangeMaterial;
local.targetPosition = block.Position; local.targetPosition = block.Position;
this.entitiesDB.PublishEntityChangeDelayed<BuildingActionComponent>(local.ID); this.entitiesDB.PublishEntityChange<BuildingActionComponent>(local.ID);
} }
//Phyiscs prefab: prefabAssetID, set on block creation from the CubeListData //Phyiscs prefab: prefabAssetID, set on block creation from the CubeListData
} }
public void UpdateBlockColor(EGID id)
{
entitiesDB.PublishEntityChangeDelayed<ColourParameterEntityStruct>(id);
}
public bool BlockExists(EGID blockID) public bool BlockExists(EGID blockID)
{ {
return entitiesDB.Exists<BlockTagEntityStruct>(blockID); return entitiesDB.Exists<DBEntityStruct>(blockID);
} }
public SimBody[] GetSimBodiesFromID(byte id) public SimBody[] GetSimBodiesFromID(byte id)
{ {
var ret = new FasterList<SimBody>(4); var ret = new FasterList<SimBody>(4);
var oids = entitiesDB.QueryEntitiesOptional<ObjectIdEntityStruct>(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP); var oide = entitiesDB.QueryEntities<ObjectIdEntityStruct>();
EGIDMapper<GridConnectionsEntityStruct>? connections = null; EGIDMapper<GridConnectionsEntityStruct>? connections = null;
foreach (var oid in oids) foreach (var ((oids, count), _) in oide)
{ {
if (oid.Get().objectId != id) continue; for (int i = 0; i < count; i++)
if (!connections.HasValue) //Would need reflection to get the group from the build group otherwise
connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(oid.EGID.groupID);
//var rid = connections.Value.Entity(tag.ID.entityID).machineRigidBodyId;
/*foreach (var rb in ret) - TODO
{ {
if (rb.Id.entityID == rid) ref ObjectIdEntityStruct oid = ref oids[i];
goto DUPLICATE; //Multiple Object Identifiers on one rigid body if (oid.objectId != id) continue;
} if (!connections.HasValue) //Would need reflection to get the group from the build group otherwise
connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(oid.ID.groupID);
var rid = connections.Value.Entity(oid.ID.entityID).machineRigidBodyId;
foreach (var rb in ret)
{
if (rb.Id.entityID == rid)
goto DUPLICATE; //Multiple Object Identifiers on one rigid body
}
ret.Add(new SimBody(rid)); ret.Add(new SimBody(rid));
DUPLICATE: ;*/ DUPLICATE: ;
}
} }
return ret.ToArray(); return ret.ToArray();
@ -196,14 +169,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 +186,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 +202,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 +215,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 +232,49 @@ 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>(); }
#endif
var ret = new FasterList<ObjectID>(4); [HarmonyPatch]
var oids = entitiesDB.QueryEntitiesOptional<ObjectIDTweakableComponent>(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP); public static class RenderingPatch
foreach (var oid in oids) {
private static ComputeRenderingEntitiesMatricesEngine Engine;
public static void Postfix(ComputeRenderingEntitiesMatricesEngine __instance)
{ {
if (oid.Get().objectIDToTrigger == id) Engine = __instance;
ret.Add(new ObjectID(oid.EGID));
} }
return ret.ToArray(); public static MethodBase TargetMethod()
{
return typeof(ComputeRenderingEntitiesMatricesEngine).GetConstructors()[0];
}
public static void UpdateBlocks()
{
var data = new RenderingDataStruct();
Engine.Add(ref data, new EGID(0, CommonExclusiveGroups.BUTTON_BLOCK_GROUP));
}
} }
} }
} }

View file

@ -26,13 +26,18 @@ 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)
{ {
if (!(shouldAddRemove = !shouldAddRemove))
return;
Placed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid}); Placed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid});
} }
public void Remove(ref BlockTagEntityStruct entityComponent, EGID egid) public void Remove(ref BlockTagEntityStruct entityComponent, EGID egid)
{ {
if (!(shouldAddRemove = !shouldAddRemove))
return;
Removed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid}); Removed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid});
} }
} }

View file

@ -1,4 +1,4 @@
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;
@ -18,10 +18,9 @@ using Svelto.ECS.DataStructures;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using Svelto.ECS.Native; using Svelto.ECS.Native;
using Svelto.ECS.Serialization; using Svelto.ECS.Serialization;
using Techblox.Blocks.Connections; using Techblox.Blocks;
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 +48,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 +88,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
public void RemoveBlockGroup(int id) public void RemoveBlockGroup(int id)
{ {
BlockGroupUtility.RemoveAllBlocksInBlockGroup(id, entitiesDB, removedConnections, nativeBlockRemove, BlockGroupUtility.RemoveAllBlocksInBlockGroup(id, entitiesDB, removedConnections, nativeRemove,
nativeConnectionRemove, connectionFactory, default).Complete(); connectionFactory, default).Complete();
} }
public int CreateBlockGroup(float3 position, quaternion rotation) public int CreateBlockGroup(float3 position, quaternion rotation)
@ -175,8 +173,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
foreach (var block in blocks) foreach (var block in blocks)
{ {
GhostChildUtility.BuildGhostChild(in playerID, block.Id, in pos, in rot, entitiesDB, GhostChildUtility.BuildGhostChild(in playerID, block.Id, in pos, in rot, entitiesDB,
BuildGhostBlueprintFactory, false, bssesopt.Get().buildingDroneReference, BuildGhostBlueprintFactory, false, bssesopt.Get().buildingDroneReference);
FullGameFields._managers.blockLabelResourceManager);
} }
} }
@ -275,11 +272,10 @@ namespace TechbloxModdingAPI.Blocks.Engines
uint ghostChildBlockId = CommonExclusiveGroups.GetNewGhostChildBlockID(); uint ghostChildBlockId = CommonExclusiveGroups.GetNewGhostChildBlockID();
var ghostEntityReference = GhostBlockUtils.GetGhostEntityReference(sourceId.entityID, entitiesDB); var ghostEntityReference = GhostBlockUtils.GetGhostEntityReference(sourceId.entityID, entitiesDB);
var entityInitializer = BuildGhostBlueprintFactory.Build( var entityInitializer = BuildGhostBlueprintFactory.Build(
new EGID(ghostChildBlockId, BoxSelectExclusiveGroups.GhostChildEntitiesExclusiveGroup), /*dbStruct.DBID*/ (uint)BlockIDs.Cube, new EGID(ghostChildBlockId, BoxSelectExclusiveGroups.GhostChildEntitiesExclusiveGroup), /*dbStruct.DBID*/ (uint)BlockIDs.Cube);
FullGameFields._managers.blockLabelResourceManager);
entityInitializer.Init(dbStruct); entityInitializer.Init(dbStruct);
entityInitializer.Init(new GFXPrefabEntityStructGPUI( entityInitializer.Init(new GFXPrefabEntityStructGPUI(
PrefabsID.GetOrAddPrefabID((ushort)entityInitializer.Get<PrefabAssetIDComponent>().prefabAssetID, PrefabsID.GetOrCreatePrefabID((ushort)entityInitializer.Get<PrefabAssetIDComponent>().prefabAssetID,
entitiesDB.QueryEntity<CubeMaterialStruct>(sourceId).materialId, 7, entitiesDB.QueryEntity<CubeMaterialStruct>(sourceId).materialId, 7,
FlippedBlockUtils.IsFlipped(in scalingEntityStruct.scale)), true)); FlippedBlockUtils.IsFlipped(in scalingEntityStruct.scale)), true));
entityInitializer.Init(entitiesDB.QueryEntity<CubeMaterialStruct>(sourceId)); entityInitializer.Init(entitiesDB.QueryEntity<CubeMaterialStruct>(sourceId));
@ -341,8 +337,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 != default)
{ //It exists { //It exists
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.Get().dotsEntity, new Translation FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Translation
{ {
Value = posStruct.position Value = posStruct.position
}); });
} }
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).areConnectionsAssigned = false; entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).isProcessed = false;
return posStruct.position; return posStruct.position;
} }

View file

@ -16,7 +16,6 @@ using Unity.Mathematics;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {
@ -55,11 +54,11 @@ namespace TechbloxModdingAPI.Blocks.Engines
//RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine //RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine
DBEntityStruct dbEntity = new DBEntityStruct {DBID = block}; DBEntityStruct dbEntity = new DBEntityStruct {DBID = block};
EntityInitializer structInitializer = _blockEntityFactory.Build(CommonExclusiveGroups.blockIDGeneratorClient.Next(), block); //The ghost block index is only used for triggers EntityInitializer structInitializer = _blockEntityFactory.Build(CommonExclusiveGroups.nextBlockEntityID, block); //The ghost block index is only used for triggers
uint prefabAssetID = structInitializer.Has<PrefabAssetIDComponent>() uint prefabAssetID = structInitializer.Has<PrefabAssetIDComponent>()
? structInitializer.Get<PrefabAssetIDComponent>().prefabAssetID ? structInitializer.Get<PrefabAssetIDComponent>().prefabAssetID
: throw new BlockException("Prefab asset ID not found!"); //Set by the game : throw new BlockException("Prefab asset ID not found!"); //Set by the game
uint prefabId = PrefabsID.GetOrAddPrefabID((ushort) prefabAssetID, (byte) BlockMaterial.SteelBodywork, 1, false); uint prefabId = PrefabsID.GetOrCreatePrefabID((ushort) prefabAssetID, (byte) BlockMaterial.SteelBodywork, 1, false);
structInitializer.Init(new GFXPrefabEntityStructGPUI(prefabId)); structInitializer.Init(new GFXPrefabEntityStructGPUI(prefabId));
structInitializer.Init(dbEntity); structInitializer.Init(dbEntity);
structInitializer.Init(new PositionEntityStruct {position = position}); structInitializer.Init(new PositionEntityStruct {position = position});

View file

@ -1,61 +1,47 @@
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 Svelto.Common;
using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Native; using Svelto.ECS.Native;
using Techblox.Blocks.Connections;
using Unity.Collections;
using Unity.Jobs;
using Allocator = Unity.Collections.Allocator;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {
public class RemovalEngine : IApiEngine, IDeterministicTimeStopped public class RemovalEngine : IApiEngine
{ {
private static IEntityFunctions _entityFunctions; private static IEntityFunctions _entityFunctions;
private static MachineGraphConnectionEntityFactory _connectionFactory; private static MachineGraphConnectionEntityFactory _connectionFactory;
private NativeHashSet<ulong> removedConnections;
public bool RemoveBlock(EGID target) public bool RemoveBlock(EGID target)
{ {
if (!entitiesDB.Exists<MachineGraphConnectionsEntityStruct>(target)) if (!entitiesDB.Exists<MachineGraphConnectionsEntityStruct>(target))
return false; return false;
using var connStructMapper = var connections = entitiesDB.QueryEntity<MachineGraphConnectionsEntityStruct>(target);
entitiesDB.QueryNativeMappedEntities<MachineGraphConnectionsEntityStruct>(GroupTag<BLOCK_TAG>.Groups, var groups = entitiesDB.FindGroups<MachineGraphConnectionsEntityStruct>();
Svelto.Common.Allocator.Temp); using var connStructMapper = //The allocator needs to be persistent because that's what is used in the Dispose() method
if (entitiesDB.TryQueryNativeMappedEntities<MachineConnectionComponent>( entitiesDB.QueryNativeMappedEntities<MachineGraphConnectionsEntityStruct>(groups, Allocator.Persistent);
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 +61,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 != default)
{ //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,7 +204,7 @@ 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;
} }
@ -216,8 +219,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
if (!entitiesDB.Exists<BlockPortsStruct>(block)) if (!entitiesDB.Exists<BlockPortsStruct>(block))
return default; return default;
var group = output var group = output
? NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group ? NamedExclusiveGroup<OutputPortsGroup>.Group
: NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group; : NamedExclusiveGroup<InputPortsGroup>.Group;
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block); BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
if (!entitiesDB.TryQueryMappedEntities<PortEntityStruct>(group, out var mapper)) if (!entitiesDB.TryQueryMappedEntities<PortEntityStruct>(group, out var mapper))
return default; return default;
@ -226,27 +229,28 @@ namespace TechbloxModdingAPI.Blocks.Engines
uint entityID = (output ? ports.firstOutputID : ports.firstInputID) + i; uint entityID = (output ? ports.firstOutputID : ports.firstInputID) + i;
if (!mapper.TryGetArrayAndEntityIndex(entityID, out var index, out var array) || if (!mapper.TryGetArrayAndEntityIndex(entityID, out var index, out var array) ||
array[index].usage != portUsage) continue; array[index].usage != portUsage) continue;
return new OptionalRef<PortEntityStruct>(array, index, new EGID(entityID, group)); return new OptionalRef<PortEntityStruct>(array, index);
} }
return default; return default;
} }
public OptionalRef<WireEntityStruct> MatchPortToWire(PortEntityStruct port, EGID blockID, out EGID wireID) public ref WireEntityStruct MatchPortToWire(PortEntityStruct port, EGID blockID, out bool exists)
{ {
var (wires, ids, count) = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group); var wires = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<WiresGroup>.Group);
for (uint i = 0; i < count; i++) var wiresB = wires.ToBuffer().buffer;
for (uint i = 0; i < wires.count; i++)
{ {
if ((wires[i].destinationPortUsage == port.usage && wires[i].destinationBlockEGID == blockID) if ((wiresB[i].destinationPortUsage == port.usage && wiresB[i].destinationBlockEGID == blockID)
|| (wires[i].sourcePortUsage == port.usage && wires[i].sourceBlockEGID == blockID)) || (wiresB[i].sourcePortUsage == port.usage && wiresB[i].sourceBlockEGID == blockID))
{ {
wireID = new EGID(ids[i], BuildModeWiresGroups.WiresGroup.Group); exists = true;
return new OptionalRef<WireEntityStruct>(wires, i); return ref wiresB[i];
} }
} }
exists = false;
wireID = default; WireEntityStruct[] defRef = new WireEntityStruct[1];
return default; return ref defRef[0];
} }
public EGID MatchBlocksToWire(EGID startBlock, EGID endBlock, byte startPort = byte.MaxValue, byte endPort = byte.MaxValue) public EGID MatchBlocksToWire(EGID startBlock, EGID endBlock, byte startPort = byte.MaxValue, byte endPort = byte.MaxValue)
@ -260,7 +264,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,23 +276,23 @@ 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; return wiresB[w].ID;
} }
} }
} }
@ -300,21 +304,23 @@ namespace TechbloxModdingAPI.Blocks.Engines
public OptionalRef<ChannelDataStruct> GetChannelDataStruct(EGID portID) public OptionalRef<ChannelDataStruct> GetChannelDataStruct(EGID portID)
{ {
var port = GetPort(portID); var port = GetPort(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) return port.firstChannelIndexCachedInSim < channels.count
? new OptionalRef<ChannelDataStruct>(channelsB.buffer, port.firstChannelIndexCachedInSim)
: default; : default;
} }
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 +329,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,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

@ -30,11 +30,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).servoVelocity; return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).servoVelocity;
} }
set set
{ {
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).servoVelocity = value; BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).servoVelocity = value;
} }
} }
@ -45,11 +45,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).minDeviation; return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).minDeviation;
} }
set set
{ {
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).minDeviation = value; BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).minDeviation = value;
} }
} }
@ -60,11 +60,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).maxDeviation; return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).maxDeviation;
} }
set set
{ {
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).maxDeviation = value; BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).maxDeviation = value;
} }
} }
@ -75,11 +75,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).reverse; return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).reverse;
} }
set set
{ {
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).reverse = value; BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).reverse = value;
} }
} }
@ -90,11 +90,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).hasProportionalInput; return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).hasProportionalInput;
} }
set set
{ {
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).hasProportionalInput = value; BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).hasProportionalInput = value;
} }
} }
@ -105,11 +105,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).directionVector; return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).directionVector;
} }
set set
{ {
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).directionVector = value; BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).directionVector = value;
} }
} }
@ -120,11 +120,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).rotationAxis; return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).rotationAxis;
} }
set set
{ {
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).rotationAxis = value; BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).rotationAxis = value;
} }
} }
@ -135,11 +135,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).forceAxis; return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).forceAxis;
} }
set set
{ {
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).forceAxis = value; BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(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(PortEntityStruct port, out bool connected)
{ {
return SignalEngine.MatchPortToWire(port, Id, out egid); return ref SignalEngine.MatchPortToWire(port, Id, out connected);
} }
/// <summary> /// <summary>

View file

@ -40,21 +40,6 @@ namespace TechbloxModdingAPI.Blocks
} }
} }
/// <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> /// <summary>
/// Gets or sets the WheelRig's MaxVelocity property. May not be saved. /// Gets or sets the WheelRig's MaxVelocity property. May not be saved.
/// </summary> /// </summary>
@ -86,22 +71,6 @@ namespace TechbloxModdingAPI.Blocks
} }
} }
/// <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> /// <summary>
/// Gets or sets the WheelRig's VelocityForMinAngle property. May not be saved. /// Gets or sets the WheelRig's VelocityForMinAngle property. May not be saved.
/// </summary> /// </summary>

View file

@ -5,7 +5,6 @@ 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
{ {
@ -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>
@ -46,9 +45,9 @@ namespace TechbloxModdingAPI.Blocks
{ {
var port = signalEngine.MatchBlockIOToPort(end, endPort, false); var port = signalEngine.MatchBlockIOToPort(end, endPort, false);
if (!port) return null; if (!port) return null;
var wire = signalEngine.MatchPortToWire(port, end.Id, out var egid); WireEntityStruct wire = signalEngine.MatchPortToWire(port, end.Id, out var exists);
return wire return exists
? new Wire(wire.Get().sourceBlockEGID, end.Id, wire.Get().sourcePortUsage, endPort, egid, false) ? new Wire(wire.sourceBlockEGID, end.Id, wire.sourcePortUsage, endPort, wire.ID, false)
: null; : null;
} }
@ -63,9 +62,9 @@ namespace TechbloxModdingAPI.Blocks
{ {
var port = signalEngine.MatchBlockIOToPort(start, startPort, true); var port = signalEngine.MatchBlockIOToPort(start, startPort, true);
if (!port) return null; if (!port) return null;
var wire = signalEngine.MatchPortToWire(port, start.Id, out var egid); WireEntityStruct wire = signalEngine.MatchPortToWire(port, start.Id, out var exists);
return wire return exists
? new Wire(start.Id, wire.Get().destinationBlockEGID, startPort, wire.Get().destinationPortUsage, egid, false) ? new Wire(start.Id, wire.destinationBlockEGID, startPort, wire.destinationPortUsage, wire.ID, false)
: null; : null;
} }
@ -133,9 +132,9 @@ namespace TechbloxModdingAPI.Blocks
this.endBlockEGID = endBlock; this.endBlockEGID = endBlock;
this.inputToOutput = inputToOutput; this.inputToOutput = inputToOutput;
this.wireEGID = wire; this.wireEGID = wire;
endPortEGID = signalEngine.MatchBlockIOToPort(startBlock, startPort, inputToOutput).EGID; endPortEGID = signalEngine.MatchBlockIOToPort(startBlock, startPort, inputToOutput).Nullable()?.ID ?? default;
if (endPortEGID == default) throw new WireInvalidException("Wire end port not found"); if (endPortEGID == default) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockIOToPort(endBlock, endPort, !inputToOutput).EGID; startPortEGID = signalEngine.MatchBlockIOToPort(endBlock, endPort, !inputToOutput).Nullable()?.ID ?? default;
if (startPortEGID == default) throw new WireInvalidException("Wire start port not found"); if (startPortEGID == default) throw new WireInvalidException("Wire start port not found");
this.startPort = startPort; this.startPort = startPort;
this.endPort = endPort; this.endPort = endPort;
@ -152,8 +151,8 @@ namespace TechbloxModdingAPI.Blocks
wireEgid, false); wireEgid, false);
} }
private Wire(WireEntityStruct wire, SignalingBlock src, SignalingBlock dest, EGID wireEgid) private Wire(WireEntityStruct wire, SignalingBlock src, SignalingBlock dest)
: this(src, dest, wire.sourcePortUsage, wire.destinationPortUsage, wireEgid, false) : this(src, dest, wire.sourcePortUsage, wire.destinationPortUsage, wire.ID, false)
{ {
} }

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
{ {
@ -13,33 +14,28 @@ namespace TechbloxModdingAPI
{ {
} }
public Cluster(uint id) : this(new EGID(id, ClustersExclusiveGroups.SIMULATION_CLUSTERS_GROUP)) public Cluster(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_CLUSTERS_GROUP))
{ {
} }
public float InitialHealth //TODO 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>
/// 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,34 @@
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 custom commands
/// </summary>
[HarmonyPatch]
static class CommandPatch
{
public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{
/*CommandLineCompositionRoot.Compose(contextHolder, stateSyncReg.enginesRoot, reloadGame, multiplayerParameters,
stateSyncReg); - uREPL C# compilation not supported anymore */
var enginesRoot = stateSyncReg.enginesRoot;
CommandManager.RegisterEngines(enginesRoot);
}
public static MethodInfo TargetMethod()
{
return AccessTools.Method(typeof(MainGameCompositionRoot), "DeterministicCompose")
.MakeGenericMethod(typeof(object));
}
}
}

View file

@ -62,14 +62,6 @@ namespace TechbloxModdingAPI
var id = initializer(this); var id = initializer(this);
if (!dict.ContainsKey(id)) // Multiple instances may be created if (!dict.ContainsKey(id)) // Multiple instances may be created
dict.Add(id, this); 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 #region ECS initializer stuff

View file

@ -6,67 +6,27 @@ using RobocraftX.FrontEnd;
using RobocraftX.StateSync; using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Schedulers; 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 EntitiesSubmissionScheduler Scheduler { get; private set; }
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 Scheduler = stateSyncReg.enginesRoot.scheduler;
/*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

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

View file

@ -1,13 +1,15 @@
using RobocraftX.Common.Input; using System;
using TechbloxModdingAPI.App; using RobocraftX.Common;
using RobocraftX.Common.Input;
using Svelto.ECS;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Input namespace TechbloxModdingAPI.Input
{ {
public static class FakeInput public static class FakeInput
{ {
internal static readonly FakeInputEngine inputEngine = new FakeInputEngine(); private static readonly FakeInputEngine inputEngine = new FakeInputEngine();
/// <summary> /// <summary>
/// Customize the local input. /// Customize the local input.
@ -101,39 +103,39 @@ namespace TechbloxModdingAPI.Input
} }
public static void ActionInput(uint playerID = uint.MaxValue, bool toggleMode = false, bool forward = false, bool backward = false, bool up = false, bool down = false, bool left = false, bool right = false, bool sprint = false, bool toggleFly = false, bool alt = false, bool primary = false, bool secondary = false, bool tertiary = false, bool primaryHeld = false, bool secondaryHeld = false, bool toggleUnitGrid = false, bool ctrl = false, bool toggleColourMode = false, bool scaleBlockUp = false, bool scaleBlockDown = false, bool rotateBlockClockwise = false, bool rotateBlockCounterclockwise = false, bool cutSelection = false, bool copySelection = false, bool deleteSelection = false) public static void ActionInput(uint playerID = uint.MaxValue, bool toggleMode = false, bool forward = false, bool backward = false, bool up = false, bool down = false, bool left = false, bool right = false, bool sprint = false, bool toggleFly = false, bool alt = false, bool primary = false, bool secondary = false, bool tertiary = false, bool primaryHeld = false, bool secondaryHeld = false, bool toggleUnitGrid = false, bool ctrl = false, bool toggleColourMode = false, bool scaleBlockUp = false, bool scaleBlockDown = false, bool rotateBlockClockwise = false, bool rotateBlockCounterclockwise = false, bool cutSelection = false, bool copySelection = false, bool deleteSelection = false)
{ // TODO: We can only alter our own inputs clientside {
ref var currentInput = ref inputEngine._localInputCache; if (playerID == uint.MaxValue)
{
playerID = inputEngine.GetLocalPlayerID();
}
ref LocalPlayerInputEntityStruct currentInput = ref inputEngine.GetPlayerInputRef(playerID);
//Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}"); //Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}");
// set inputs // set inputs
if (toggleMode) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningModePlay; //TODO: Test, play if (toggleMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningMode;
if (forward) currentInput |= RobocraftX.Common.Input.ActionInput.Forward; if (forward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Forward;
if (backward) currentInput |= RobocraftX.Common.Input.ActionInput.Backward; if (backward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Backward;
if (up) currentInput |= RobocraftX.Common.Input.ActionInput.Up; if (up) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Up;
if (down) currentInput |= RobocraftX.Common.Input.ActionInput.Down; if (down) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Down;
if (left) currentInput |= RobocraftX.Common.Input.ActionInput.Left; if (left) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Left;
if (right) currentInput |= RobocraftX.Common.Input.ActionInput.Right; if (right) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Right;
if (sprint) currentInput |= RobocraftX.Common.Input.ActionInput.Sprint; if (sprint) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Sprint;
//if (toggleFly) currentInput |= RobocraftX.Common.Input.ActionInput.SwitchFlyMode; //if (toggleFly) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SwitchFlyMode;
//if (alt) currentInput |= RobocraftX.Common.Input.ActionInput.AltAction; //if (alt) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.AltAction;
if (primary) currentInput |= RobocraftX.Common.Input.ActionInput.PrimaryActionClick; if (primary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.PrimaryAction;
if (secondary) currentInput |= RobocraftX.Common.Input.ActionInput.SecondaryActionClick; if (secondary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SecondaryAction;
if (tertiary) currentInput |= RobocraftX.Common.Input.ActionInput.TertiaryAction; if (tertiary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.TertiaryAction;
if (primaryHeld) currentInput |= RobocraftX.Common.Input.ActionInput.PrimaryActionHeld; if (primaryHeld) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.PrimaryActionHeld;
if (secondaryHeld) currentInput |= RobocraftX.Common.Input.ActionInput.SecondaryActionHeld; if (secondaryHeld) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SecondaryActionHeld;
//if (toggleUnitGrid) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleUnitGrid; //if (toggleUnitGrid) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleUnitGrid;
if (ctrl) currentInput |= RobocraftX.Common.Input.ActionInput.CtrlAction; if (ctrl) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CtrlAction;
if (toggleColourMode) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleColourMode; if (toggleColourMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleColourMode;
if (scaleBlockUp) currentInput |= RobocraftX.Common.Input.ActionInput.ScaleBlockUp; if (scaleBlockUp) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ScaleBlockUp;
if (scaleBlockDown) currentInput |= RobocraftX.Common.Input.ActionInput.ScaleBlockDown; if (scaleBlockDown) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ScaleBlockDown;
if (rotateBlockClockwise) currentInput |= RobocraftX.Common.Input.ActionInput.RotateBlockClockwise; if (rotateBlockClockwise) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.RotateBlockClockwise;
if (rotateBlockCounterclockwise) currentInput |= RobocraftX.Common.Input.ActionInput.RotateBlockAnticlockwise; if (rotateBlockCounterclockwise) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.RotateBlockAnticlockwise;
if (cutSelection) currentInput |= RobocraftX.Common.Input.ActionInput.CutSelection; if (cutSelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CutSelection;
if (copySelection) currentInput |= RobocraftX.Common.Input.ActionInput.CopySelection; if (copySelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CopySelection;
if (deleteSelection) currentInput |= RobocraftX.Common.Input.ActionInput.DeleteSelection; if (deleteSelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.DeleteSelection;
if(Game.CurrentGame().IsTimeStopped)
inputEngine.HandleCustomInput(); // Only gets called when online, so calling it here as well
} }
public static void Init() public static void Init()

View file

@ -20,8 +20,6 @@ namespace TechbloxModdingAPI.Input
public bool IsReady = false; public bool IsReady = false;
internal ActionInput _localInputCache;
public void Dispose() public void Dispose()
{ {
IsReady = false; IsReady = false;
@ -88,14 +86,6 @@ namespace TechbloxModdingAPI.Input
return ref entitiesDB.QueryEntity<LocalPlayerInputEntityStruct>(egid); return ref entitiesDB.QueryEntity<LocalPlayerInputEntityStruct>(egid);
} }
internal void HandleCustomInput()
{
if (!LocalPlayerIDUtility.DoesLocalPlayerExist(entitiesDB))
return;
GetPlayerInputRef(GetLocalPlayerID()).actionMask |= _localInputCache;
_localInputCache = default;
}
public uint GetLocalPlayerID() public uint GetLocalPlayerID()
{ {
return LocalPlayerIDUtility.GetLocalPlayerID(entitiesDB); return LocalPlayerIDUtility.GetLocalPlayerID(entitiesDB);

View file

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

View file

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

View file

@ -20,7 +20,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// </summary> /// </summary>
public Rect Box { get; set; } public Rect Box { get; set; }
public override void OnGUI() public void OnGUI()
{ {
/*if (Constants.Default == null) return; /*if (Constants.Default == null) return;
if (Constants.Default.box == null) return;*/ if (Constants.Default.box == null) return;*/
@ -61,6 +61,16 @@ namespace TechbloxModdingAPI.Interface.IMGUI
} }
} }
/// <summary>
/// The group's unique name.
/// </summary>
public string Name { get; }
/// <summary>
/// Whether to display the group and everything in it.
/// </summary>
public bool Enabled { set; get; } = true;
/// <summary> /// <summary>
/// The amount of elements in the group. /// The amount of elements in the group.
/// </summary> /// </summary>
@ -75,10 +85,19 @@ namespace TechbloxModdingAPI.Interface.IMGUI
/// <param name="box">The rectangular area to use in the window.</param> /// <param name="box">The rectangular area to use in the window.</param>
/// <param name="name">Name of the group.</param> /// <param name="name">Name of the group.</param>
/// <param name="automaticLayout">Whether to use automatic UI layout.</param> /// <param name="automaticLayout">Whether to use automatic UI layout.</param>
public Group(Rect box, string name = null, bool automaticLayout = false) : base(box.ToString().Replace(" ", ""), name) public Group(Rect box, string name = null, bool automaticLayout = false)
{ {
Box = box; Box = box;
if (name == null)
{
this.Name = typeof(Group).FullName + "::" + box.ToString().Replace(" ", "");
}
else
{
this.Name = name;
}
this.automaticLayout = automaticLayout; this.automaticLayout = automaticLayout;
IMGUIManager.AddElement(this);
} }
/// <summary> /// <summary>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,22 +20,6 @@ namespace TechbloxModdingAPI
add => seatExited += value; add => seatExited += value;
remove => 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 struct PlayerSeatEventArgs
@ -43,10 +27,4 @@ namespace TechbloxModdingAPI
public EGID SeatId; public EGID SeatId;
public Seat Seat => (Seat)Block.New(SeatId); public Seat Seat => (Seat)Block.New(SeatId);
} }
public struct PlayerEventArgs
{
public EGID PlayerId;
public Player Player => Player.GetInstance(PlayerId.entityID);
}
} }

View file

@ -10,7 +10,6 @@ using RobocraftX.Physics;
using Svelto.ECS; using Svelto.ECS;
using Techblox.BuildingDrone; using Techblox.BuildingDrone;
using Techblox.Camera; using Techblox.Camera;
using Techblox.Character;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Players; using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -24,8 +23,8 @@ namespace TechbloxModdingAPI
public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID> public partial class Player : EcsObjectBase, 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 PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine();
private static Player localPlayer; private static Player localPlayer;
/// <summary> /// <summary>
@ -65,27 +64,18 @@ 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>
@ -129,7 +119,6 @@ namespace TechbloxModdingAPI
}) })
{ {
this.Type = player; this.Type = player;
Id = base.Id.entityID;
} }
// object fields & properties // object fields & properties
@ -195,8 +184,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 +197,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 +210,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 +229,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 +260,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>
@ -359,18 +363,6 @@ namespace TechbloxModdingAPI
public PlayerBuildingMode BuildingMode => (PlayerBuildingMode)Math.Log((double)playerEngine public PlayerBuildingMode BuildingMode => (PlayerBuildingMode)Math.Log((double)playerEngine
.GetCharacterStruct<TimeStoppedModeComponent>(Id).Get().timeStoppedContext, 2); // It's a bit field in game now .GetCharacterStruct<TimeStoppedModeComponent>(Id).Get().timeStoppedContext, 2); // It's a bit field in game now
public PlayerState State =>
playerEngine.GetCharacterStruct<CharacterTagEntityStruct>(Id).Get().ID.groupID switch
{
var group when group == CharacterExclusiveGroups.MachineSpawningGroup => PlayerState.HoldingMachine,
var group when group == CharacterExclusiveGroups.OnFootGroup => PlayerState.OnFoot,
var group when group == CharacterExclusiveGroups.InPilotSeatGroup => PlayerState.InSeat,
var group when group == CharacterExclusiveGroups.DyingOnFootGroup => PlayerState.OnFoot,
var group when group == CharacterExclusiveGroups.DyingInPilotSeatGroup => PlayerState.InSeat,
var group when group == CharacterExclusiveGroups.DeadGroup => PlayerState.OnFoot,
_ => throw new ArgumentOutOfRangeException("", "Unknown player state")
};
/// <summary> /// <summary>
/// Whether the player is sprinting. /// Whether the player is sprinting.
/// </summary> /// </summary>
@ -443,39 +435,16 @@ 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) public void EnterSeat(Seat seat)
{ {
playerEngine.EnterSeat(Id, seat.Id); playerEngine.EnterSeat(Id, seat.Id);
} }
/// <summary>
/// Exit the seat the player is currently in.
/// </summary>
public void ExitSeat() public void ExitSeat()
{ {
playerEngine.ExitSeat(Id); 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>
@ -512,7 +481,7 @@ namespace TechbloxModdingAPI
{ {
var egid = playerEngine.GetThingLookedAt(Id, maxDistance); var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
return egid != default && egid.groupID == WiresGUIExclusiveGroups.WireGroup return egid != default && egid.groupID == WiresGUIExclusiveGroups.WireGroup
? EcsObjectBase.GetInstance(new EGID(egid.entityID, BuildModeWiresGroups.WiresGroup.Group), ? EcsObjectBase.GetInstance(new EGID(egid.entityID, NamedExclusiveGroup<WiresGroup>.Group),
e => new Wire(e)) e => new Wire(e))
: null; : null;
} }
@ -526,15 +495,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;

View file

@ -8,20 +8,18 @@ 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.Blocks;
using RobocraftX.SimulationModeState; using RobocraftX.PilotSeat;
using Svelto.ECS; using Svelto.ECS;
using Techblox.Camera; using Techblox.Camera;
using Unity.Mathematics; using Unity.Mathematics;
using Svelto.ECS.DataStructures; using Svelto.ECS.DataStructures;
using Svelto.ECS.EntityStructs;
using Techblox.BuildingDrone; using Techblox.BuildingDrone;
using Techblox.Character;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Players namespace TechbloxModdingAPI.Players
{ {
@ -50,10 +48,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 +59,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;
} }
@ -113,10 +111,18 @@ namespace TechbloxModdingAPI.Players
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
@ -165,9 +171,8 @@ namespace TechbloxModdingAPI.Players
var opt = GetCameraStruct<PhysicCameraRayCastEntityStruct>(playerId); var opt = GetCameraStruct<PhysicCameraRayCastEntityStruct>(playerId);
if (!opt) return default; if (!opt) return default;
PhysicCameraRayCastEntityStruct rayCast = opt; PhysicCameraRayCastEntityStruct rayCast = opt;
EGID physicCameraEgid = new EGID(playerId, CameraExclusiveGroups.PhysicCameraGroup);
float distance = maxDistance < 0 float distance = maxDistance < 0
? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast, physicCameraEgid, ? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast,
GhostBlockUtils.GhostCastMethod.GhostCastProportionalToBlockSize) GhostBlockUtils.GhostCastMethod.GhostCastProportionalToBlockSize)
: maxDistance; : maxDistance;
if (rayCast.hit && rayCast.distance <= distance) if (rayCast.hit && rayCast.distance <= distance)
@ -199,11 +204,9 @@ namespace TechbloxModdingAPI.Players
public void EnterSeat(uint playerId, EGID seatId) public void EnterSeat(uint playerId, EGID seatId)
{ {
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB)) PilotSeatGroupUtils.SwapTagTo<OCCUPIED_TAG>(Functions, seatId);
return;
/*PilotSeatGroupUtils.SwapTagTo<OCCUPIED_TAG>(Functions, seatId);
var opt = GetCharacterStruct<CharacterPilotSeatEntityStruct>(playerId, out var group); 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. if (!opt) return;
ref CharacterPilotSeatEntityStruct charSeat = ref opt.Get(); ref CharacterPilotSeatEntityStruct charSeat = ref opt.Get();
var charId = new EGID(playerId, group); var charId = new EGID(playerId, group);
charSeat.pilotSeatEntity = entitiesDB.GetEntityReference(seatId); charSeat.pilotSeatEntity = entitiesDB.GetEntityReference(seatId);
@ -213,62 +216,16 @@ namespace TechbloxModdingAPI.Players
ref var seat = ref entitiesDB.QueryEntity<PilotSeatEntityStruct>(seatId); ref var seat = ref entitiesDB.QueryEntity<PilotSeatEntityStruct>(seatId);
seat.occupyingCharacter = entitiesDB.GetEntityReference(charId); seat.occupyingCharacter = entitiesDB.GetEntityReference(charId);
charSeat.followCam = entitiesDB.QueryEntity<SeatFollowCamComponent>(seatId).followCam; charSeat.followCam = entitiesDB.QueryEntity<SeatFollowCamComponent>(seatId).followCam;
Functions.SwapEntityGroup<CharacterEntityDescriptor>(charId, CharacterExclusiveGroups.InPilotSeatGroup);*/ Functions.SwapEntityGroup<CharacterEntityDescriptor>(charId, CharacterExclusiveGroups.InPilotSeatGroup);
} }
public void ExitSeat(uint playerId) public void ExitSeat(uint playerId)
{ {
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB)) EGID egid = new EGID(playerId, CharacterExclusiveGroups.InPilotSeatGroup);
return;
/*EGID egid = new EGID(playerId, CharacterExclusiveGroups.InPilotSeatGroup);
var opt = entitiesDB.QueryEntityOptional<CharacterPilotSeatEntityStruct>(egid); var opt = entitiesDB.QueryEntityOptional<CharacterPilotSeatEntityStruct>(egid);
if (!opt) return; if (!opt) return;
opt.Get().instantExit = true; opt.Get().instantExit = true;
entitiesDB.PublishEntityChange<CharacterPilotSeatEntityStruct>(egid);*/ 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,14 +1,11 @@
using System;
using RobocraftX.Character; using RobocraftX.Character;
using RobocraftX.Character.Movement; using RobocraftX.Character.Movement;
using RobocraftX.Common.Input;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Players namespace TechbloxModdingAPI.Players
{ {
public class PlayerEventsEngine : IApiEngine, IReactOnSwap<CharacterPilotSeatEntityStruct>, IReactOnAddAndRemove<PlayerIDStruct> public class PlayerEventsEngine : IApiEngine, IReactOnSwap<CharacterPilotSeatEntityStruct>
{ {
public void Ready() public void Ready()
{ {
@ -24,22 +21,13 @@ namespace TechbloxModdingAPI.Players
public void MovedTo(ref CharacterPilotSeatEntityStruct entityComponent, ExclusiveGroupStruct previousGroup, EGID egid) public void MovedTo(ref CharacterPilotSeatEntityStruct entityComponent, ExclusiveGroupStruct previousGroup, EGID egid)
{ {
entitiesDB.TryGetEGID(entityComponent.pilotSeatEntity, out var seatId); //TODO: Can't get EGID var seatId = entityComponent.pilotSeatEntity.ToEGID(entitiesDB);
var player = Player.GetInstance(egid.entityID); var player = EcsObjectBase.GetInstance(new EGID(egid.entityID, CharacterExclusiveGroups.OnFootGroup),
e => new Player(e.entityID));
if (previousGroup == CharacterExclusiveGroups.InPilotSeatGroup) if (previousGroup == CharacterExclusiveGroups.InPilotSeatGroup)
player.seatExited.Invoke(this, new PlayerSeatEventArgs { SeatId = seatId}); player.seatExited.Invoke(this, new PlayerSeatEventArgs { SeatId = seatId});
else if (egid.groupID == CharacterExclusiveGroups.InPilotSeatGroup) else if (egid.groupID == CharacterExclusiveGroups.InPilotSeatGroup)
player.seatEntered.Invoke(this, new PlayerSeatEventArgs { SeatId = seatId }); 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,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using Svelto.Tasks; using Svelto.Tasks;
using Svelto.Tasks.Enumerators; using Svelto.Tasks.Enumerators;
@ -8,7 +7,6 @@ using Unity.Mathematics;
using TechbloxModdingAPI.App; using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Players namespace TechbloxModdingAPI.Players
{ {
@ -29,7 +27,7 @@ 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;
@ -39,27 +37,15 @@ namespace TechbloxModdingAPI.Players
[APITestCase(TestType.Game)] [APITestCase(TestType.Game)]
public static void SeatEventTestBuild() public static void SeatEventTestBuild()
{ {
Block.PlaceNew(BlockIDs.DriverSeat, Player.LocalPlayer.Position); Player.LocalPlayer.SeatEntered += Assert.CallsBack<PlayerSeatEventArgs>("SeatEntered");
Player.LocalPlayer.SeatExited += Assert.CallsBack<PlayerSeatEventArgs>("SeatExited");
Block.PlaceNew(BlockIDs.DriverSeat, -1f);
} }
[APITestCase(TestType.SimulationMode)] [APITestCase(TestType.SimulationMode)]
public static IEnumerator<TaskContract> SeatEventTestSim() 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); 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) if (seats.Length == 0)
{ {
Assert.Fail("No driver seat found!"); Assert.Fail("No driver seat found!");
@ -67,21 +53,11 @@ namespace TechbloxModdingAPI.Players
} }
if (seats[0] is Seat seat) if (seats[0] is Seat seat)
{
Assert.Errorless(() => Player.LocalPlayer.EnterSeat(seat), "Failed to enter seat.", Assert.Errorless(() => Player.LocalPlayer.EnterSeat(seat), "Failed to enter seat.",
"Entered seat successfully."); "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 else
Assert.Fail("Found a seat that is not a seat!"); Assert.Fail("Found a seat that is not a seat!");
yield return new WaitForSecondsEnumerator(5).Continue(); yield return new WaitForSecondsEnumerator(1).Continue();
Assert.Errorless(() => Player.LocalPlayer.ExitSeat(), "Failed to exit seat.", Assert.Errorless(() => Player.LocalPlayer.ExitSeat(), "Failed to exit seat.",
"Exited seat successfully."); "Exited seat successfully.");
} }

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
{ {
@ -21,7 +20,7 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public Cluster Cluster => cluster ??= clusterId == uint.MaxValue // Return cluster or if it's null then set it public Cluster Cluster => cluster ??= clusterId == uint.MaxValue // Return cluster or if it's null then set it
? Block.BlockEngine.GetCluster(Id.entityID) // If we don't have a clusterId set then get it from the game ? 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), : GetInstance(new EGID(clusterId, CommonExclusiveGroups.SIMULATION_CLUSTERS_GROUP),
egid => new Cluster(egid)); // Otherwise get the cluster from the ID egid => new Cluster(egid)); // Otherwise get the cluster from the ID
private Cluster cluster; private Cluster cluster;
@ -74,16 +73,15 @@ namespace TechbloxModdingAPI
} }
} }
[Obsolete] //Cannot get mass even from UECS
public float Mass public float Mass
{ {
get => 0f; get => math.rcp(GetStruct().physicsMass.InverseMass);
//set => GetStruct().physicsMass.InverseMass = math.rcp(value); //set => GetStruct().physicsMass.InverseMass = math.rcp(value);
} }
public float3 CenterOfMass public float3 CenterOfMass
{ {
get => 0f; //TODO get => GetStruct().physicsMass.CenterOfMass;
//set => GetStruct().physicsMass.CenterOfMass = value; //set => GetStruct().physicsMass.CenterOfMass = value;
} }
@ -94,20 +92,20 @@ namespace TechbloxModdingAPI
public float InitialHealth public float InitialHealth
{ {
get => 0f; get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth;
set { } set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth = value;
} }
public float CurrentHealth public float CurrentHealth
{ {
get => 0f; get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth;
set { } set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth = value;
} }
public float HealthMultiplier public float HealthMultiplier
{ {
get => 0f; get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier;
set { } set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier = value;
} }
/// <summary> /// <summary>

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,84 +1,388 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using HarmonyLib;
using RobocraftX.FrontEnd;
using TechbloxModdingAPI.App; using TechbloxModdingAPI.App;
using HarmonyLib;
using IllusionInjector;
// test
using RobocraftX.FrontEnd;
using Unity.Mathematics;
using UnityEngine;
using RobocraftX.Common.Input;
using Svelto.Tasks;
using Svelto.Tasks.Lean;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Commands; using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Tests namespace TechbloxModdingAPI.Tests
{ {
#if DEBUG // The API should be loaded by other plugins, but it can be used by itself for testing #if DEBUG
[BepInPlugin("org.exmods.TechbloxModdingAPIPluginTest", PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] // unused by design
[BepInProcess("Techblox.exe")] /// <summary>
public class TechbloxModdingAPIPluginTest : BaseUnityPlugin /// Modding API implemented as a standalone IPA Plugin.
/// Ideally, TechbloxModdingAPI should be loaded by another mod; not itself
/// </summary>
class TechbloxModdingAPIPluginTest : IllusionPlugin.IEnhancedPlugin
{ {
private void Awake()
{
Main.Init();
Client.EnterMenu += (sender, args) => throw new Exception("Test handler always throws an exception!");
Client.EnterMenu += (sender, args) => Console.WriteLine("EnterMenu handler after erroring handler");
Game.Enter += (s, a) =>
{
Player.LocalPlayer.SeatEntered += (sender, args) =>
Console.WriteLine($"Player {Player.LocalPlayer} entered seat {args.Seat}");
Player.LocalPlayer.SeatExited += (sender, args) =>
Console.WriteLine($"Player {Player.LocalPlayer} exited seat {args.Seat}");
};
CommandBuilder.Builder() private static Harmony harmony { get; set; }
.Name("Exit")
.Description("Close Techblox immediately, without any prompts")
.Action(() => { UnityEngine.Application.Quit(); })
.Build();
CommandBuilder.Builder() public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
.Name("SetFOV")
.Description("Set the player camera's field of view")
.Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
.Build();
Game.AddPersistentDebugInfo("InstalledMods", InstalledMods); public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
// Plugin startup logic public string HarmonyID { get; } = "org.git.exmods.modtainers.techbloxmoddingapi";
Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!");
#if TEST public override void OnApplicationQuit()
TestRoot.RunTests();
#endif
}
private void OnDestroy()
{ {
Main.Shutdown(); Main.Shutdown();
} }
private string modsString; public override void OnApplicationStart()
{
FileLog.Reset();
Harmony.DEBUG = true;
Main.Init();
Logging.MetaDebugLog($"Version group id {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!");
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}");
};
// debug/test commands
if (Dependency.Hell("ExtraCommands"))
{
CommandBuilder.Builder()
.Name("Exit")
.Description("Close Techblox immediately, without any prompts")
.Action(() => { UnityEngine.Application.Quit(); })
.Build();
CommandBuilder.Builder()
.Name("SetFOV")
.Description("Set the player camera's field of view")
.Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
.Build();
CommandBuilder.Builder()
.Name("MoveLastBlock")
.Description("Move the most-recently-placed block, and any connected blocks by the given offset")
.Action((float x, float y, float z) =>
{
if (GameState.IsBuildMode())
foreach (var block in Block.GetLastPlacedBlock().GetConnectedCubes())
block.Position += new Unity.Mathematics.float3(x, y, z);
else
Logging.CommandLogError("Blocks can only be moved in Build mode!");
}).Build();
CommandBuilder.Builder()
.Name("PlaceAluminium")
.Description("Place a block of aluminium at the given coordinates")
.Action((float x, float y, float z) =>
{
var block = Block.PlaceNew(BlockIDs.Cube, new float3(x, y, z));
Logging.CommandLog("Block placed with type: " + block.Type);
})
.Build();
CommandBuilder.Builder()
.Name("PlaceAluminiumLots")
.Description("Place a lot of blocks of aluminium at the given coordinates")
.Action((float x, float y, float z) =>
{
Logging.CommandLog("Starting...");
var sw = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
for (int j = 0; j < 100; j++)
Block.PlaceNew(BlockIDs.Cube, new float3(x + i, y, z + j));
sw.Stop();
Logging.CommandLog("Finished in " + sw.ElapsedMilliseconds + "ms");
})
.Build();
Block b = null;
CommandBuilder.Builder("moveBlockInSim", "Run in build mode first while looking at a block, then in sim to move it up")
.Action(() =>
{
if (b == null)
{
b = new Player(PlayerType.Local).GetBlockLookedAt();
Logging.CommandLog("Block saved: " + b);
}
else
Logging.CommandLog("Block moved to: " + (b.GetSimBody().Position += new float3(0, 2, 0)));
}).Build();
CommandBuilder.Builder("Error", "Throw an error to make sure SimpleCustomCommandEngine's wrapper catches it.")
.Action(() => { throw new Exception("Error Command always throws an error"); })
.Build();
CommandBuilder.Builder("ColorBlock",
"Change color of the block looked at if there's any.")
.Action<string>(str =>
{
if (!Enum.TryParse(str, out BlockColors color))
{
Logging.CommandLog("Color " + str + " not found! Interpreting as 4 color values.");
var s = str.Split(' ');
new Player(PlayerType.Local).GetBlockLookedAt().CustomColor = new float4(float.Parse(s[0]),
float.Parse(s[1]), float.Parse(s[2]), float.Parse(s[3]));
return;
}
new Player(PlayerType.Local).GetBlockLookedAt().Color = color;
Logging.CommandLog("Colored block to " + color);
}).Build();
CommandBuilder.Builder("MoveBlockByID", "Gets a block based on its object identifier and teleports it up.")
.Action<char>(ch =>
{
foreach (var body in SimBody.GetFromObjectID(ch))
{
Logging.CommandLog("SimBody: " + body);
body.Position += new float3(0, 10, 0);
foreach (var bodyConnectedBody in body.GetConnectedBodies())
{
Logging.CommandLog("Moving " + bodyConnectedBody);
bodyConnectedBody.Position += new float3(0, 10, 0);
}
}
}).Build();
CommandBuilder.Builder("TestChunkHealth", "Sets the chunk looked at to the given health.")
.Action((float val, float max) =>
{
var body = new Player(PlayerType.Local).GetSimBodyLookedAt();
if (body == null) return;
body.CurrentHealth = val;
body.InitialHealth = max;
Logging.CommandLog("Health set to: " + val);
}).Build();
CommandBuilder.Builder("placeBlockGroup", "Places some blocks in a group")
.Action((float x, float y, float z) =>
{
var pos = new float3(x, y, z);
var group = BlockGroup.Create(new Block(BlockIDs.Cube, pos) {Color = BlockColors.Aqua});
new Block(BlockIDs.Cube, pos += new float3(1, 0, 0))
{Color = BlockColors.Blue, BlockGroup = group};
new Block(BlockIDs.Cube, pos += new float3(1, 0, 0))
{Color = BlockColors.Green, BlockGroup = group};
new Block(BlockIDs.Cube, pos + new float3(1, 0, 0))
{Color = BlockColors.Lime, BlockGroup = group};
}).Build();
CommandBuilder.Builder("placeCustomBlock", "Places a custom block, needs a custom catalog and assets.")
.Action((float x, float y, float z) =>
{
Logging.CommandLog("Block placed: " +
Block.PlaceNew((BlockIDs) 500, new float3(0, 0, 0)));
}).Build();
CommandBuilder.Builder("toggleTimeMode", "Enters or exits simulation.")
.Action((float x, float y, float z) =>
{
Game.CurrentGame().ToggleTimeMode();
}).Build();
CommandBuilder.Builder("testColorBlock", "Tests coloring a block to default color")
.Action(() => Player.LocalPlayer.GetBlockLookedAt().Color = BlockColors.Default).Build();
CommandBuilder.Builder("testMaterialBlock", "Tests materialing a block to default material")
.Action(() => Player.LocalPlayer.GetBlockLookedAt().Material = BlockMaterial.Default).Build();
CommandBuilder.Builder("testGameName", "Tests changing the game name")
.Action(() => Game.CurrentGame().Name = "Test").Build();
CommandBuilder.Builder("makeBlockStatic", "Makes a block you look at static")
.Action(() => Player.LocalPlayer.GetBlockLookedAt().Static = true).Build();
Game.AddPersistentDebugInfo("InstalledMods", InstalledMods);
/*Block.Placed += (sender, args) =>
Logging.MetaDebugLog("Placed block " + args.Block);
Block.Removed += (sender, args) =>
Logging.MetaDebugLog("Removed block " + args.Block);*/
}
// dependency test
if (Dependency.Hell("TechbloxScripting", new Version("0.0.1.0")))
{
Logging.LogWarning("You're in TechbloxScripting dependency hell");
}
else
{
Logging.Log("Compatible TechbloxScripting detected");
}
// Interface test
/*Group uiGroup = new Group(new Rect(20, 20, 200, 500), "TechbloxModdingAPI_UITestGroup", true);
var button = new Button("TEST");
button.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
var button2 = new Button("TEST2");
button2.OnClick += (b, __) => { Logging.MetaDebugLog($"Click on {((Interface.IMGUI.Button)b).Name}");};
Text uiText = new Text("<Input!>", multiline: true);
uiText.OnEdit += (t, txt) => { Logging.MetaDebugLog($"Text in {((Text)t).Name} is now '{txt}'"); };
Label uiLabel = new Label("Label!");
Image uiImg = new Image(name:"Behold this texture!");
uiImg.Enabled = false;
uiGroup.AddElement(button);
uiGroup.AddElement(button2);
uiGroup.AddElement(uiText);
uiGroup.AddElement(uiLabel);
uiGroup.AddElement(uiImg);*/
/*Addressables.LoadAssetAsync<Texture2D>("Assets/Art/Textures/UI/FrontEndMap/RCX_Blue_Background_5k.jpg")
.Completed +=
handle =>
{
uiImg.Texture = handle.Result;
uiImg.Enabled = true;
Logging.MetaDebugLog($"Got blue bg asset {handle.Result}");
};*/
/*((FasterList<GuiInputMap.GuiInputMapElement>)AccessTools.Property(typeof(GuiInputMap), "GuiInputsButtonDown").GetValue(null))
.Add(new GuiInputMap.GuiInputMapElement(RewiredConsts.Action.ToggleCommandLine, GuiIn))*/
/*Game.Enter += (sender, e) =>
{
ushort lastKey = ushort.MaxValue;
foreach (var kv in FullGameFields._dataDb.GetValues<CubeListData>()
.OrderBy(kv=>ushort.Parse(kv.Key)))
{
var data = (CubeListData) kv.Value;
ushort currentKey = ushort.Parse(kv.Key);
var toReplace = new Dictionary<string, string>
{
{"Scalable", ""}, {"Qtr", "Quarter"}, {"RNeg", "Rounded Negative"},
{"Neg", "Negative"}, {"Tetra", "Tetrahedron"},
{"RWedge", "Rounded Wedge"}, {"RTetra", "Rounded Tetrahedron"}
};
string name = LocalizationService.Localize(data.CubeNameKey).Split(' ').Select(str =>
str.Length > 0 ? char.ToUpper(str[0]) + str.Substring(1) : str).Aggregate((a, b) => a + b)
.Replace("-", "");
foreach (var rkv in toReplace)
{
name = Regex.Replace(name, rkv.Key + "([A-Z]|$)", rkv.Value + "$1");
}
Console.WriteLine($"{name}{(currentKey != lastKey + 1 ? $" = {currentKey}" : "")},");
lastKey = currentKey;
}
};*/
/*Game.Enter += (sender, e) =>
{
ushort lastKey = ushort.MaxValue;
Console.WriteLine("Materials:\n" + FullGameFields._dataDb.GetValues<MaterialPropertiesData>()
.OrderBy(kv => ushort.Parse(kv.Key))
.Select(kv =>
{
ushort currentKey = ushort.Parse(kv.Key);
string result = $"{((MaterialPropertiesData)kv.Value).Name}{(currentKey != lastKey + 1 ? $" = {kv.Key}" : "")},";
lastKey = currentKey;
return result;
})
.Aggregate((a, b) => a + "\n" + b));
};*/
CommandBuilder.Builder("takeScreenshot", "Enables the screenshot taker")
.Action(() =>
{
Game.CurrentGame().EnableScreenshotTaker();
}).Build();
CommandBuilder.Builder("testPositionDefault", "Tests the Block.Position property's default value.")
.Action(() =>
{
IEnumerator<TaskContract> Loop()
{
for (int i = 0; i < 2; i++)
{
Console.WriteLine("A");
var block = Block.PlaceNew(BlockIDs.Cube, 1);
Console.WriteLine("B");
while (!block.Exists)
yield return Yield.It;
Console.WriteLine("C");
block.Remove();
Console.WriteLine("D");
while (block.Exists)
yield return Yield.It;
Console.WriteLine("E - Pos: " + block.Position);
block.Position = 4;
Console.WriteLine("F - Pos: " + block.Position);
}
}
Loop().RunOn(Scheduler.leanRunner);
}).Build();
CommandBuilder.Builder("importAssetBundle")
.Action(() =>
{
Logging.CommandLog("Importing asset bundle...");
var ab = AssetBundle.LoadFromFile(
@"filepath");
Logging.CommandLog("Imported asset bundle: " + ab);
var assets = ab.LoadAllAssets();
Logging.CommandLog("Loaded " + assets.Length + " assets");
foreach (var asset in assets)
{
Logging.CommandLog(asset);
}
}).Build();
#if TEST
TestRoot.RunTests();
#endif
}
private string modsString;
private string InstalledMods() private string InstalledMods()
{ {
if (modsString != null) return modsString; if (modsString != null) return modsString;
StringBuilder sb = new StringBuilder("Installed mods:"); StringBuilder sb = new StringBuilder("Installed mods:");
foreach (var (_, plugin) in Chainloader.PluginInfos) foreach (var plugin in PluginManager.Plugins)
sb.Append("\n" + plugin.Metadata.Name + " - " + plugin.Metadata.Version); sb.Append("\n" + plugin.Name + " - " + plugin.Version);
return modsString = sb.ToString(); return modsString = sb.ToString();
}
}
[HarmonyPatch]
public class MinimumSpecsPatch
{
public static bool Prefix(ref bool __result)
{
__result = true;
return false;
} }
public static MethodInfo TargetMethod() public override void OnUpdate()
{ {
return ((Func<bool>) MinimumSpecsCheck.CheckRequirementsMet).Method; if (UnityEngine.Input.GetKeyDown(KeyCode.End))
{
Console.WriteLine("Pressed button to toggle console");
FakeInput.CustomInput(new LocalCosmeticInputEntityComponent {commandLineToggleInput = true});
}
}
[HarmonyPatch]
public class MinimumSpecsPatch
{
public static bool Prefix(ref bool __result)
{
__result = true;
return false;
}
public static MethodInfo TargetMethod()
{
return ((Func<bool>) 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,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,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;
@ -98,6 +104,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 +136,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 +160,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;

View file

@ -1,7 +1,7 @@
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Hybrid; using Svelto.ECS.Hybrid;
namespace TechbloxModdingAPI.Utility.ECS namespace TechbloxModdingAPI.Utility
{ {
public static class ManagedApiExtensions public static class ManagedApiExtensions
{ {
@ -29,8 +29,7 @@ namespace TechbloxModdingAPI.Utility.ECS
/// <param name="group">The group of the entity if the object can have multiple</param> /// <param name="group">The group of the entity if the object can have multiple</param>
/// <typeparam name="T">The component to query</typeparam> /// <typeparam name="T">The component to query</typeparam>
/// <returns>A reference to the component or a dummy value</returns> /// <returns>A reference to the component or a dummy value</returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj, public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj, ExclusiveGroupStruct group = default)
ExclusiveGroupStruct group = default)
where T : struct, IEntityViewComponent where T : struct, IEntityViewComponent
{ {
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group); EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
@ -46,30 +45,14 @@ namespace TechbloxModdingAPI.Utility.ECS
/// <param name="group">The group of the entity if the object can have multiple</param> /// <param name="group">The group of the entity if the object can have multiple</param>
/// <typeparam name="T">The component to query</typeparam> /// <typeparam name="T">The component to query</typeparam>
/// <returns>A reference to the component or a dummy value</returns> /// <returns>A reference to the component or a dummy value</returns>
public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj, public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj, ExclusiveGroupStruct group = default)
ExclusiveGroupStruct group = default)
where T : struct, IEntityViewComponent where T : struct, IEntityViewComponent
{ {
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group); EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
var opt = QueryEntityOptional<T>(entitiesDB, id); var opt = QueryEntityOptional<T>(entitiesDB, id);
if (opt) return ref opt.Get(); if (opt) return ref opt.Get();
if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrAdd<T>(); if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrCreate<T>();
return ref opt.Get(); //Default value 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,14 +1,8 @@
using System;
using System.Collections.Generic;
using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using Svelto.Tasks;
using Svelto.Tasks.Lean;
using TechbloxModdingAPI.Tasks;
namespace TechbloxModdingAPI.Utility.ECS namespace TechbloxModdingAPI.Utility
{ {
public static partial class NativeApiExtensions public static class NativeApiExtensions
{ {
/// <summary> /// <summary>
/// Attempts to query an entity and returns an optional that contains the result if succeeded. /// Attempts to query an entity and returns an optional that contains the result if succeeded.
@ -34,8 +28,7 @@ namespace TechbloxModdingAPI.Utility.ECS
/// <param name="group">The group of the entity if the object can have multiple</param> /// <param name="group">The group of the entity if the object can have multiple</param>
/// <typeparam name="T">The component to query</typeparam> /// <typeparam name="T">The component to query</typeparam>
/// <returns>A reference to the component or a dummy value</returns> /// <returns>A reference to the component or a dummy value</returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj, public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj, ExclusiveGroupStruct group = default)
ExclusiveGroupStruct group = default)
where T : unmanaged, IEntityComponent where T : unmanaged, IEntityComponent
{ {
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group); EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
@ -51,14 +44,13 @@ namespace TechbloxModdingAPI.Utility.ECS
/// <param name="group">The group of the entity if the object can have multiple</param> /// <param name="group">The group of the entity if the object can have multiple</param>
/// <typeparam name="T">The component to query</typeparam> /// <typeparam name="T">The component to query</typeparam>
/// <returns>A reference to the component or a dummy value</returns> /// <returns>A reference to the component or a dummy value</returns>
public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj, public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj, ExclusiveGroupStruct group = default)
ExclusiveGroupStruct group = default)
where T : unmanaged, IEntityComponent where T : unmanaged, IEntityComponent
{ {
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group); EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
var opt = QueryEntityOptional<T>(entitiesDB, id); var opt = QueryEntityOptional<T>(entitiesDB, id);
if (opt) return ref opt.Get(); if (opt) return ref opt.Get();
if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrAdd<T>(); if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrCreate<T>();
/*if (!obj.InitData.Valid) return ref opt.Get(); //Default value /*if (!obj.InitData.Valid) return ref opt.Get(); //Default value
var init = obj.InitData.Initializer(id); 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 // Do not create the component if missing, as that can trigger Add() listeners that, in some cases, may be
@ -66,64 +58,5 @@ namespace TechbloxModdingAPI.Utility.ECS
if (init.Has<T>()) return ref init.Get<T>();*/ if (init.Has<T>()) return ref init.Get<T>();*/
return ref opt.Get(); //Default value 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

@ -4,9 +4,8 @@ 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 +13,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 +35,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 +49,6 @@ namespace TechbloxModdingAPI.Utility
} }
array = default; array = default;
index = default; index = default;
managedArray = default;
} }
/// <summary> /// <summary>
@ -65,25 +59,11 @@ namespace TechbloxModdingAPI.Utility
{ {
CompRefCache.Default = default; //The default value can be changed by mods CompRefCache.Default = default; //The default value can be changed by mods
if (state == State.Empty) return ref CompRefCache.Default; if (state == State.Empty) return ref CompRefCache.Default;
if ((state & State.Initializer) != State.Empty) return ref initializer.GetOrAdd<T>(); if ((state & State.Initializer) != State.Empty) return ref initializer.GetOrCreate<T>();
if ((state & State.Native) != State.Empty) return ref array[index]; if ((state & State.Native) != State.Empty) return ref array[index];
return ref managedArray[index]; return ref managedArray[index];
} }
/// <summary>
/// A non-by-ref view that allows getting and setting the value.
/// </summary>
public T Value
{
get => Get();
set => Get() = value;
}
/// <summary>
/// The ID of the entity this component belongs to or default if it doesn't exist.
/// </summary>
public EGID EGID => entityId;
public bool Exists => state != State.Empty; public bool Exists => state != State.Empty;
public T? Nullable() => this ? Get() : default; public T? Nullable() => this ? Get() : default;
@ -91,8 +71,6 @@ namespace TechbloxModdingAPI.Utility
public static implicit operator bool(OptionalRef<T> opt) => opt.state != State.Empty; public static implicit operator bool(OptionalRef<T> opt) => opt.state != State.Empty;
public static implicit operator EGID(OptionalRef<T> opt) => opt.entityId;
/// <summary> /// <summary>
/// Creates an instance of a struct T that can be referenced. /// Creates an instance of a struct T that can be referenced.
/// </summary> /// </summary>

View file

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

View file

@ -7,7 +7,7 @@ namespace TechbloxModdingAPI.Utility
{ {
public class WeakDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TValue : class public class WeakDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TValue : class
{ {
private readonly Dictionary<TKey, WeakReference<TValue>> _dictionary = new(); private Dictionary<TKey, WeakReference<TValue>> _dictionary = new Dictionary<TKey, WeakReference<TValue>>();
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{ {
@ -19,26 +19,58 @@ namespace TechbloxModdingAPI.Utility
} }
} }
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator()
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value); {
public void Clear() => _dictionary.Clear(); return GetEnumerator();
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) => public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Clear()
{
_dictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return TryGetValue(item.Key, out var value) && item.Value == value;
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
throw new System.NotImplementedException(); throw new System.NotImplementedException();
public bool Remove(KeyValuePair<TKey, TValue> item) => Contains(item) && Remove(item.Key); }
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Contains(item) && Remove(item.Key);
}
public int Count => _dictionary.Count; public int Count => _dictionary.Count;
public bool IsReadOnly => false; 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 ContainsKey(TKey key)
public bool Remove(TKey key) => _dictionary.Remove(key); {
return _dictionary.ContainsKey(key);
}
public void Add(TKey key, TValue value)
{
_dictionary.Add(key, new WeakReference<TValue>(value));
}
public bool Remove(TKey key)
{
return _dictionary.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value) public bool TryGetValue(TKey key, out TValue value)
{ {
value = null; value = null;
bool ret = _dictionary.TryGetValue(key, out var reference) && reference.TryGetTarget(out value); return _dictionary.TryGetValue(key, out var reference) && reference.TryGetTarget(out value);
if (!ret) _dictionary.Remove(key);
return ret;
} }
public TValue this[TKey key] public TValue this[TKey key]
@ -49,37 +81,12 @@ namespace TechbloxModdingAPI.Utility
set => _dictionary[key] = new WeakReference<TValue>(value); set => _dictionary[key] = new WeakReference<TValue>(value);
} }
public ICollection<TKey> Keys => new KeyCollection(this); public ICollection<TKey> Keys => _dictionary.Keys;
public ICollection<TValue> Values => new ValueCollection(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> public class ValueCollection : ICollection<TValue>, IReadOnlyCollection<TValue>
{ {
private readonly WeakDictionary<TKey, TValue> _dictionary; private WeakDictionary<TKey, TValue> _dictionary;
internal ValueCollection(WeakDictionary<TKey, TValue> dictionary) internal ValueCollection(WeakDictionary<TKey, TValue> dictionary)
{ {
_dictionary = dictionary; _dictionary = dictionary;
@ -89,15 +96,41 @@ namespace TechbloxModdingAPI.Utility
{ {
using var enumerator = _dictionary.GetEnumerator(); using var enumerator = _dictionary.GetEnumerator();
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{
yield return enumerator.Current.Value; yield return enumerator.Current.Value;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return 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)
{
return _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.");
} }
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 int Count => _dictionary.Count;
public bool IsReadOnly => true; public bool IsReadOnly => true;
} }

View file

@ -34,12 +34,6 @@ namespace TechbloxModdingAPI.Utility
Logging.LogWarning(wrappedException.ToString()); Logging.LogWarning(wrappedException.ToString());
} }
}; };
if (wrappers.ContainsKey(added))
{
original.eventHandler -= wrapped;
wrappers.Remove(added);
}
wrappers.Add(added, wrapped); wrappers.Add(added, wrapped);
return new WrappedHandler<T> { eventHandler = original.eventHandler + wrapped }; return new WrappedHandler<T> { eventHandler = original.eventHandler + wrapped };
} }

View file

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