Compare commits

..

64 commits

Author SHA1 Message Date
5dff88d703
Switch from IPA to BepInEx
- Removed a bunch of test code
- Preparing for 3.0
2023-08-22 00:02:26 +02:00
a8a451f8e4
Merge TB update feature branch 2023-03-30 01:22:10 +02:00
67f32b8810
Improved and fixed publish queue detection and block test
- Made the PublishEntityChangesDelayed() method use the internals of Svelto.ECS to determine if it should wait
-- I had this code for a while but it used too much reflection to my liking
-- Now I made the reflection code nicer and a bit safer
- Fixed float comparisons: last time I didn't actually used abs() for float3 but I found this even better method
2023-03-30 01:17:31 +02:00
b3b1e9b9e7
Update reference paths to allow for RC2 dev as well 2022-10-18 20:19:41 +02:00
e0cd7f6aec
Fix assembly editing and add more of it
- It breaks the game atm, not sure why exactly but it's probably not a good thing
2022-10-05 01:53:54 +02:00
23439abde3
Add new blocks and materials, make every type public in the game, fix entity publish
- Probably should've committed more
- Added new block IDs and a material (also fixed material names)
- Added missing player states
- Added a class to make every type public in the game's code, so we don't need to worry about internal components
- We don't need to worry about anticheat so it should be fine - will need to be made into its own exe though
- Fixed delayed entity publishing and set the limits based on the consumers (just 30 almost everywhere)
2022-10-04 01:47:09 +02:00
5e90c5ee26
Fix all compiler issues and add Count property and smart ToArray() function to RefCollection
- Collections can be converted into arrays using a mapper and a predicate function
2022-10-02 01:34:51 +02:00
5117b69500
Fix RefCollection and start using it to query multiple users
- I overcomplicated in the beginning
- It doesn't shorten the code that much but it provides a stable interface and it's easier to use so I guess it's nice
2022-09-29 01:26:51 +02:00
f70b65e796
Start updating to Techblox 2022.08.11.09.42 and start work on RefCollection
What have I got myself into
2022-09-29 00:29:12 +02:00
55344d1352
Start updating to Techblox 2022.05.25.11.05
Resolved compiler errors
Mostly by removing erroring code
2022-06-01 16:54:17 +02:00
dfe1bfb504
Begin updating to Techblox 2022.04.28.14.02
Updated project generator script to always order assemblies (it didn't do that for me on Linux) and to fix minor issues
2022-04-29 02:07:46 +02:00
a610623644 Bump version 2022-04-12 03:18:28 +02:00
f9aa6ce2bb Re-add object ID class, add some wheel rig properties, remove old game assembly refernces 2022-04-12 00:52:24 +02:00
23abe47c72 Update to Techblox 2022.04.01.10.32
- Updated project to use .NET Standard 2.1, which is what the game uses
- Updated CodeGenerator to use .NET 6
2022-04-08 03:25:05 +02:00
c0ef8f1fae Fix support for accessing properties using reflection
The test still crashes the game
2022-03-27 03:49:45 +02:00
c4a9125ed3 Update to Techblox 2022.03.17.17.24 2022-03-20 18:08:16 +01:00
3eecdf2cf5 Add key collection to weak dictionary and compact code 2022-02-24 01:02:35 +01:00
2db7b607f0 Improve UI elements (IMGUI) 2022-02-23 02:25:34 +01:00
7f63944a6e Block fixes, add mass and complexity properties, make Player.LocalPlayer return null if not found 2022-02-19 02:25:58 +01:00
c6dae688fe Update to Techblox 2022.02.17.10.32 2022-02-18 23:09:56 +01:00
7b2ac973d8 Bump version to v2.2.0 2022-02-13 20:21:42 +01:00
0ec47cd38b Add method to get ghost block 2022-02-13 18:27:54 +01:00
ddaa933e7d Add option to delay entity change publish and remove reflection stuff
Neither of them work actually
Added some delay between tests
2022-02-07 00:25:01 +01:00
5fea7dc3b3 Add support for generating block classes that use reflection to access internal components
Added Engine properties again
2022-02-06 03:11:51 +01:00
4684b33c69 Fix tests, getting machine blocks, block labels and visuals
- Checking the material property again, it seems to work now
- Fixed the Seat events not triggering during tests (the player in build and in sim is different)
- Fixed Game.GetAllBlocksInGame() returning environment blocks (since a Game refers to a machine save)
- Fixed the Block.Label property
- Fixed the block visuals not being updated after applying changes
2022-01-31 23:20:03 +01:00
d27bcee8d5 Update to Techblox 2022.01.25.15.52
- Fixed compilation errors
- Fixed patching errors and added missing anti-cheat patch
- Added check to verify that the init data has been removed from blocks once they are placed in game
- Removed block place event deduplication as it seems to be not needed anymore
- Fixed async tests not properly running
- Added Player.State
- Attempted to fix seat entering/leaving (we can only send inputs in client code)
- Fixed the weak dictionary ContainsKey returning true even if the item is no longer there
2022-01-30 04:32:10 +01:00
09d3c5e81c Merge branch 'preview'
# Conflicts:
#	Automation/gen_csproj.py
#	TechbloxModdingAPI/TechbloxModdingAPI.csproj
2022-01-29 20:53:07 +01:00
966fdd4c3a Fix even more issues uncovered by tests
- Fixed the time mode toggle not working during testing (changed the runner)
- Made the testing thing wait until the time toggle finishes
- Fixed the Game.Enter/Exit event being triggered on time mode change
- Added a way to spawn and despawn the player's machine (which doesn't work yet)
- Fixed the Player.Id property always being 0
- Attempted to fix the fake action inputs not working in simulation
2022-01-07 02:14:58 +01:00
5602ef9268 All kinds of fixes of issues during automatic tests
- Fixed toggling time running mode
- Fixed closing popups
- Added support for pressing the buttons on a popup
- Added error handling to Main.Init()
- Automatically closing the beta message in the test plugin
- Fixed Game.EnterGame() causing a crash in the game
2021-12-28 15:09:01 +01:00
93a0b2287a Added player join/leave events and fix errors
- Fixed anticheat status error spam
- Fixed IMGUI not actually running on OnGUI because that runner was changed in Svelto
- Added player join and leave events
- Made Game.Enter only trigger when both the game has finished loading *and* the local player has joined
2021-12-27 02:28:09 +01:00
4ac8d53a2d Organize anti-anticheat, add block IDs, fix crash when adding event handlers multiple times 2021-12-26 23:37:02 +01:00
f817becc6e Resolve all compile-time and patching errors, remove anticheat in singleplayer 2021-12-16 21:13:45 +01:00
2a1782cd82 Start updating to 2021.12.14.17.00
A bunch of errors still
2021-12-15 03:46:38 +01:00
fef66c349d Merge branch 'master' into preview
# Conflicts:
#	Automation/gen_csproj.py
#	GamecraftModdingAPI/App/AppEngine.cs
#	GamecraftModdingAPI/App/GameGameEngine.cs
#	GamecraftModdingAPI/App/GameMenuEngine.cs
#	GamecraftModdingAPI/Block.cs
#	GamecraftModdingAPI/Blocks/BlockEngine.cs
#	GamecraftModdingAPI/Blocks/BlockEngineInit.cs
#	GamecraftModdingAPI/Blocks/BlockEventsEngine.cs
#	GamecraftModdingAPI/Blocks/BlockIDs.cs
#	GamecraftModdingAPI/Blocks/ConsoleBlock.cs
#	GamecraftModdingAPI/Blocks/DampedSpring.cs
#	GamecraftModdingAPI/Blocks/LogicGate.cs
#	GamecraftModdingAPI/Blocks/Motor.cs
#	GamecraftModdingAPI/Blocks/MusicBlock.cs
#	GamecraftModdingAPI/Blocks/ObjectIdentifier.cs
#	GamecraftModdingAPI/Blocks/Piston.cs
#	GamecraftModdingAPI/Blocks/PlacementEngine.cs
#	GamecraftModdingAPI/Blocks/Servo.cs
#	GamecraftModdingAPI/Blocks/SfxBlock.cs
#	GamecraftModdingAPI/Blocks/SpawnPoint.cs
#	GamecraftModdingAPI/Blocks/TextBlock.cs
#	GamecraftModdingAPI/Blocks/Timer.cs
#	GamecraftModdingAPI/GamecraftModdingAPI.csproj
#	GamecraftModdingAPI/Inventory/HotbarEngine.cs
#	GamecraftModdingAPI/Inventory/HotbarSlotSelectionHandlerEnginePatch.cs
#	GamecraftModdingAPI/Main.cs
#	GamecraftModdingAPI/Player.cs
#	GamecraftModdingAPI/Players/PlayerEngine.cs
#	GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
#	TechbloxModdingAPI/BlockGroup.cs
#	TechbloxModdingAPI/Blocks/Engines/BlueprintEngine.cs
#	TechbloxModdingAPI/Blocks/Engines/RemovalEngine.cs
#	TechbloxModdingAPI/Blocks/Engines/SignalEngine.cs
#	TechbloxModdingAPI/Blueprint.cs
#	TechbloxModdingAPI/Input/FakeInput.cs
2021-12-14 23:26:35 +01:00
4580ae3b66 Add ability to create & move block groups & other stuff
Added a way to store block groups as blueprints
Blocks can be added/removed from block groups, although it doesn't work well atm
Added some patches to the test class in an attempt to debug an unrelated issue
Added a command to test placing a block group
Added a SelectedBlueprint property to the Player class
2020-11-12 02:39:58 +01:00
f1376f5df6 Replace ToManagedArray() and fix getting blocks from group 2020-11-10 23:08:27 +01:00
2179ba6386 Add support for setting and placing blueprints 2020-11-10 19:28:36 +01:00
1a986056a1 Add new blocks and some blueprint/block group support 2020-11-10 16:37:20 +01:00
NGnius (Graham)
d891f12701 Fix harmony patch error due to fixed name 2020-11-09 16:33:12 -05:00
NGnius (Graham)
1cb663b4d1 Fix build errors from beta hotfix 1 2020-11-09 16:18:25 -05:00
0bd348bd47
Fix initial issues and add error on patch fail
Fixed compilation and loading issues for 2020.10.27.17.13
2020-10-29 00:37:47 +01:00
3929144171
Merge remote-tracking branch 'origin/master' into preview
# Conflicts:
#	GamecraftModdingAPI/Block.cs
#	GamecraftModdingAPI/GamecraftModdingAPI.csproj
2020-10-28 21:10:30 +01:00
92965404ce Remove ScalingEngine.Setup() and add object ID to dict 2020-10-02 01:54:59 +02:00
58cfba443e Add hotfix blocks and Player.LocalPlayer 2020-09-30 23:52:17 +02:00
ee6a0e3af6 Add support for getting the RGB of block colors
Only works if the constructors are used
2020-09-28 03:10:59 +02:00
NGnius (Graham)
9e6edc19bd Implement SFX block API and bump version 2020-09-23 15:31:54 -04:00
d581ec598a Add the rest of the blocks 2020-09-19 00:13:05 +02:00
1e9d1c8f81 Fix TextBlock.Text=null, most new blocks and others 2020-09-18 21:19:39 +02:00
53bdd27166 Merge master into preview 2020-09-18 17:10:01 +02:00
2172364d26 Fixes, block IDs, cluster & chunk health support 2020-08-13 16:59:13 +02:00
NGnius (Graham)
50ebf4f0a6 Fix build issues for latest Gamecraft preview version 2020-08-07 13:55:00 -04:00
NGnius (Graham)
167ea5388b Merge branch 'master' into preview 2020-08-07 12:23:16 -04:00
47126d2d79
Update music block and attempt to fix test 2020-07-21 02:36:11 +02:00
c5e9599c46
Merge branch 'delegating' into preview 2020-07-21 00:24:50 +02:00
3f2139d592
Add some info and prev. value for setters 2020-07-21 00:19:30 +02:00
NGnius (Graham)
9e47bbcd9a Add popup UI method to Client 2020-07-19 20:05:01 -04:00
NGnius (Graham)
cda57afade Add sfx block support 2020-07-19 16:39:35 -04:00
NGnius (Graham)
4a9ceecc29 Improve dev info in README 2020-07-19 12:43:06 -04:00
16521ab7eb Remove initializer data once the block is placed 2020-07-19 01:42:32 +02:00
cc4ed3e174 Test fixes, block event Block property
Fixed Assert.Equal()
Changed tests to reflect changes
Added Block property to the block event args
Completely removed sync things
2020-07-19 01:13:39 +02:00
NGnius (Graham)
e0aa052305 Fix dev tools for preview changes 2020-07-16 20:38:51 -04:00
d842df7681 Implement init for position and rotation 2020-07-15 22:46:48 +02:00
3592c6f464 Add support for initializing blocks with properties
Newly created blocks use the initializer to set properties, allowing the user to set per-block properties
2020-07-15 21:58:24 +02:00
5bbb54c0c5 Automatically invoke the correct block constructor
And store delegates of dynamic methods invoking constructors
Tested with the automated tests
2020-07-13 21:55:48 +02:00
83 changed files with 5483 additions and 2714 deletions

View file

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

10
CodeGenerator/App.config Normal file
View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,35 +0,0 @@
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

@ -0,0 +1,4 @@
<?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

@ -0,0 +1,25 @@
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,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechbloxModdingAPI", "Techb
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGenerator", "CodeGenerator\CodeGenerator.csproj", "{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MakeEverythingPublicInGame", "MakeEverythingPublicInGame\MakeEverythingPublicInGame.csproj", "{391A3107-E5C6-4A04-9467-6D868AA9A8B4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -26,6 +28,12 @@ Global
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Release|Any CPU.Build.0 = Release|Any CPU
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Test|Any CPU.ActiveCfg = Debug|Any CPU
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Test|Any CPU.Build.0 = Debug|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Release|Any CPU.Build.0 = Release|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Test|Any CPU.ActiveCfg = Debug|Any CPU
{391A3107-E5C6-4A04-9467-6D868AA9A8B4}.Test|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -0,0 +1,58 @@
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

@ -1,44 +0,0 @@
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>
public class Client
{
public static Client Instance { get; } = new Client();
protected static Func<object> ErrorHandlerInstanceGetter;
protected static Action<object, Error> EnqueueError;
protected static Action<object> HandleErrorClosed;
/// <summary>
/// An event that fires whenever the main menu is loaded.
/// </summary>
@ -93,14 +93,26 @@ namespace TechbloxModdingAPI.App
EnqueueError(errorHandlerInstance, popup);
}
// TODO
/*public void CloseCurrentPrompt()
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();
HandleErrorClosed(errorHandlerInstance);
}*/
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.Close();
}
public void SelectFirstPromptButton()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.FirstButton();
}
public void SelectSecondPromptButton()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.SecondButton();
}
internal static void Init()
{
@ -113,9 +125,6 @@ namespace TechbloxModdingAPI.App
EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError")
.MakeGenericMethod(errorHandler, errorHandle)
.Invoke(null, new object[0]);
/*HandleErrorClosed = (Action<object>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenHandlePopupClosed")
.MakeGenericMethod(errorHandler)
.Invoke(null, new object[0]);*/
}
// Creating delegates once is faster than reflection every time
@ -140,14 +149,23 @@ namespace TechbloxModdingAPI.App
return enqueueCasted;
}
private static Action<object> GenHandlePopupClosed<T>()
private static (Action Close, Action FirstButton, Action SecondButton) _errorPopup;
private static (Action Close, Action FirstButton, Action SecondButton) GetPopupCloseMethods(object handler)
{
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
MethodInfo handlePopupClosed = AccessTools.Method(errorHandler, "HandlePopupClosed");
Action<T> handleSimple =
(Action<T>) Delegate.CreateDelegate(typeof(Action<T>), handlePopupClosed);
Action<object> handleCasted = (object instance) => handleSimple((T) instance);
return handleCasted;
if (_errorPopup.Close != null)
return _errorPopup;
Type errorHandler = handler.GetType();
FieldInfo field = AccessTools.Field(errorHandler, "errorPopup");
var errorPopup = (ErrorPopup)field.GetValue(handler);
MethodInfo info = AccessTools.Method(errorPopup.GetType(), "ClosePopup");
var close = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleFirstOption");
var first = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleSecondOption");
var second = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
_errorPopup = (close, first, second);
return _errorPopup;
}
}
}

View file

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

View file

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

View file

@ -2,12 +2,10 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using RobocraftX.Common;
using RobocraftX.GUI.MyGamesScreen;
using RobocraftX.StateSync;
using Svelto.ECS;
using Techblox.GameSelection;
using TechbloxModdingAPI;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility;
@ -318,7 +316,7 @@ namespace TechbloxModdingAPI.App
get
{
if (menuMode || !VerifyMode()) return CurrentGameMode.None;
return (CurrentGameMode) GameMode.CurrentMode;
return gameEngine.GetGameData().gameMode == GameMode.CreateWorld ? CurrentGameMode.Build : CurrentGameMode.Play;
}
}

View file

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

View file

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

View file

@ -8,10 +8,10 @@ using Svelto.ECS.EntityStructs;
using RobocraftX.Common;
using RobocraftX.Blocks;
using Unity.Mathematics;
using Gamecraft.Blocks.GUI;
using HarmonyLib;
using RobocraftX.PilotSeat;
using RobocraftX.Rendering;
using Techblox.BlockLabelsServer;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Tests;
@ -67,7 +67,7 @@ namespace TechbloxModdingAPI
/// <returns>The block object or null if doesn't exist</returns>
public static Block GetLastPlacedBlock()
{
uint lastBlockID = (uint) AccessTools.Field(typeof(CommonExclusiveGroups), "_nextBlockEntityID").GetValue(null) - 1;
uint lastBlockID = CommonExclusiveGroups.blockIDGeneratorClient.Peek() - 1;
EGID? egid = BlockEngine.FindBlockEGID(lastBlockID);
return egid.HasValue ? New(egid.Value) : null;
}
@ -288,6 +288,7 @@ namespace TechbloxModdingAPI
color.indexInPalette = value.Index;
color.hasNetworkChange = true;
color.paletteColour = BlockEngine.ConvertBlockColor(color.indexInPalette); //Setting to 255 results in black
BlockEngine.UpdateBlockColor(Id);
}
}
@ -339,11 +340,15 @@ namespace TechbloxModdingAPI
[TestValue(null)]
public string Label
{
get => BlockEngine.GetBlockInfoViewComponent<TextLabelEntityViewStruct>(this).textLabelComponent?.text;
get
{
var opt = BlockEngine.GetBlockInfoOptional<LabelResourceIDComponent>(this);
return opt ? FullGameFields._managers.blockLabelResourceManager.GetText(opt.Get().instanceID) : null;
}
set
{
var comp = BlockEngine.GetBlockInfoViewComponent<TextLabelEntityViewStruct>(this).textLabelComponent;
if (comp != null) comp.text = value;
var opt = BlockEngine.GetBlockInfoOptional<LabelResourceIDComponent>(this);
if (opt) FullGameFields._managers.blockLabelResourceManager.SetText(opt.Get().instanceID, value);
}
}
@ -361,8 +366,12 @@ namespace TechbloxModdingAPI
get
{
if (blockGroup != null) return blockGroup;
if (!GameState.IsBuildMode()) return null; // Breaks in simulation
var bgec = BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this);
return blockGroup = bgec.currentBlockGroup == -1 ? null : new BlockGroup(bgec.currentBlockGroup, this);
return blockGroup = bgec.currentBlockGroup == -1
? null
: GetInstance(new EGID((uint)bgec.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup),
egid => new BlockGroup((int)egid.entityID, this));
}
set
{
@ -390,6 +399,23 @@ namespace TechbloxModdingAPI
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>
/// 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.
@ -415,9 +441,10 @@ namespace TechbloxModdingAPI
public SimBody GetSimBody()
{
var st = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(this);
return st.machineRigidBodyId != uint.MaxValue
? new SimBody(st.machineRigidBodyId, st.clusterId)
: null;
/*return st.machineRigidBodyId != uint.MaxValue
? new SimBody(st.machineRigidBodyId, st.clusterId) - TODO:
: null;*/
return null;
}
/// <summary>

View file

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

View file

@ -0,0 +1,17 @@
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,6 +273,111 @@ namespace TechbloxModdingAPI.Blocks
/// <summary>
/// The grid block used by the world editor, named Small Grid like the other one
/// </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,5 +31,16 @@ namespace TechbloxModdingAPI.Blocks
SteelBodyworkPaintedChipped,
WoodPainted,
WoodRoughGrungy,
Boundary,
Emissive,
AircraftPanelingRivetedPainted,
AircraftPanelingRivetedMetallic,
SteelBodyworkPearlescent,
SteelBodyworkRadWrap,
SteelBodyworkGlitter,
BouncyRubber,
BouncyRubberTieDye,
BrickPainted,
FuturisticPanelingRivetedPainted,
}
}

View file

@ -5,11 +5,8 @@ using System.Reflection;
using DataLoader;
using Svelto.Tasks;
using Svelto.Tasks.Enumerators;
using Unity.Mathematics;
using UnityEngine;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility;
@ -82,42 +79,50 @@ namespace TechbloxModdingAPI.Blocks
yield break;
for (var index = 0; index < blocks.Length; index++)
{
if (index % 50 == 0) yield return Yield.It; //The material or flipped status can only be changed 130 times per submission
var block = blocks[index];
if (!block.Exists) continue;
foreach (var property in block.GetType().GetProperties())
{
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
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)
(typeof(long), 3, null),
(typeof(int), 4, null),
(typeof(double), 5.2f, obj => Math.Abs((double) obj - 5.2f) < float.Epsilon),
(typeof(float), 5.2f, obj => Math.Abs((float) obj - 5.2f) < float.Epsilon),
(typeof(bool), true, obj => (bool) obj),
(typeof(string), "Test", obj => (string) obj == "Test"), //String equality check
(typeof(float3), (float3) 2, obj => math.all((float3) obj - 2 < (float3) float.Epsilon)),
(typeof(double), 5.2f, t => Math.Abs((double) t.Value - (double) t.Default) < float.Epsilon),
(typeof(float), 5.2f, t => Math.Abs((float) t.Value - (float) t.Default) < float.Epsilon),
(typeof(bool), true, t => (bool) t.Value),
(typeof(string), "Test", t => (string) t.Value == "Test"), //String equality check
(typeof(float3), (float3) 20, t => math.all(Float3Compare((float3)t.Value, (float3)t.Default))),
(typeof(BlockColor), new BlockColor(BlockColors.Aqua, 2), null),
(typeof(float4), (float4) 5, obj => math.all((float4) obj - 5 < (float4) float.Epsilon))
(typeof(float4), (float4) 5, t => math.all(Float4Compare((float4)t.Value, (float4)t.Default)))
};
var propType = property.PropertyType;
if (!propType.IsValueType) continue;
(object valueToUse, Predicate<object> predicateToUse) = (null, null);
(object valueToUse, Predicate<(object Value, object Default)> predicateToUse) = (null, null);
foreach (var (type, value, predicate) in testValues)
{
if (type.IsAssignableFrom(propType))
{
valueToUse = value;
predicateToUse = predicate ?? (obj => Equals(obj, value));
predicateToUse = predicate ?? (t => Equals(t.Value, t.Default));
break;
}
}
@ -126,7 +131,7 @@ namespace TechbloxModdingAPI.Blocks
{
var values = propType.GetEnumValues();
valueToUse = values.GetValue(values.Length / 2);
predicateToUse = val => Equals(val, valueToUse);
predicateToUse = t => Equals(t.Value, t.Default);
}
if (valueToUse == null)
@ -154,7 +159,7 @@ namespace TechbloxModdingAPI.Blocks
continue;
}
var attr = property.GetCustomAttribute<TestValueAttribute>();
if (!predicateToUse(got) && (attr == null || !Equals(attr.PossibleValue, got)))
if (!predicateToUse((got, valueToUse)) && (attr == null || !Equals(attr.PossibleValue, got)))
{
Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}.");
yield break;

View file

@ -23,7 +23,7 @@ namespace TechbloxModdingAPI.Blocks
{
}
/// <summary>
/*/// <summary> - TODO: Internal struct access
/// Gets or sets the Engine's On property. May not be saved.
/// </summary>
public bool On
@ -377,6 +377,6 @@ namespace TechbloxModdingAPI.Blocks
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime = value;
}
}
}*/
}
}

View file

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

View file

@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using Gamecraft.ColourPalette;
using Gamecraft.TimeRunning;
using Gamecraft.Wires;
using RobocraftX.Blocks;
using RobocraftX.Common;
@ -14,12 +13,16 @@ using RobocraftX.Rendering.GPUI;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Svelto.ECS.Experimental;
using Svelto.ECS.Hybrid;
using Techblox.BuildingDrone;
using Techblox.ObjectIDBlockServer;
using Unity.Mathematics;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
using PrefabsID = RobocraftX.Common.PrefabsID;
namespace TechbloxModdingAPI.Blocks.Engines
{
@ -44,22 +47,21 @@ namespace TechbloxModdingAPI.Blocks.Engines
public Block[] GetConnectedBlocks(EGID blockID)
{
if (!BlockExists(blockID)) return new Block[0];
if (!BlockExists(blockID)) return Array.Empty<Block>();
Stack<EGID> cubeStack = new Stack<EGID>();
FasterList<EGID> cubes = new FasterList<EGID>(10);
var coll = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
foreach (var (ecoll, _) in coll)
foreach (var ((ecoll, count), _) in coll)
{
var ecollB = ecoll.ToBuffer();
for(int i = 0; i < ecoll.count; i++)
for(int i = 0; i < count; i++)
{
ref var conn = ref ecollB.buffer[i];
conn.isProcessed = false;
ecoll[i].areConnectionsAssigned = false;
}
}
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubes,
(in GridConnectionsEntityStruct g) => { return false; });
//TODO: GetConnectedCubesUtility
/*ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubes,
(in GridConnectionsEntityStruct _) => false);*/
var ret = new Block[cubes.count];
for (int i = 0; i < cubes.count; i++)
@ -70,7 +72,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
public float4 ConvertBlockColor(byte index) => index == byte.MaxValue
? new float4(-1f, -1f, -1f, -1f)
: entitiesDB.QueryEntity<PaletteEntryEntityStruct>(index,
CommonExclusiveGroups.COLOUR_PALETTE_GROUP).Colour;
ColourPaletteExclusiveGroups.COLOUR_PALETTE_GROUP).Colour;
public OptionalRef<T> GetBlockInfoOptional<T>(Block block) where T : unmanaged, IEntityComponent
{
@ -79,6 +81,10 @@ namespace TechbloxModdingAPI.Blocks.Engines
public ref T GetBlockInfo<T>(Block block) where T : unmanaged, IEntityComponent
{
#if DEBUG
if (!typeof(BlockTagEntityStruct).IsAssignableFrom(typeof(T)) && block.Exists && block.InitData.Valid)
throw new ArgumentException("The block exists but the init data has not been removed!");
#endif
return ref entitiesDB.QueryEntityOrDefault<T>(block);
}
@ -92,6 +98,25 @@ namespace TechbloxModdingAPI.Blocks.Engines
return ref entitiesDB.QueryEntityOrDefault<T>(block);
}
internal object GetBlockInfo(Block block, Type type, string name)
{
var opt = AccessTools.Method(typeof(NativeApiExtensions), "QueryEntityOptional",
new[] { typeof(EntitiesDB), typeof(EcsObjectBase), typeof(ExclusiveGroupStruct) }, new[] { type })
.Invoke(null, new object[] { entitiesDB, block, null });
var str = AccessTools.Property(opt.GetType(), "Value").GetValue(opt);
return AccessTools.Field(str.GetType(), name).GetValue(str);
}
internal void SetBlockInfo(Block block, Type type, string name, object value)
{
var opt = AccessTools.Method(typeof(BlockEngine), "GetBlockInfoOptional", generics: new[] { type })
.Invoke(this, new object[] { block });
var prop = AccessTools.Property(opt.GetType(), "Value");
var str = prop.GetValue(opt);
AccessTools.Field(str.GetType(), name).SetValue(str, value);
prop.SetValue(opt, str);
}
public void UpdateDisplayedBlock(EGID id)
{
if (!BlockExists(id)) return;
@ -101,6 +126,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
var skew = entitiesDB.QueryEntity<SkewComponent>(id);
entitiesDB.QueryEntity<RenderingDataStruct>(id).matrix =
math.mul(float4x4.TRS(pos.position, rot.rotation, scale.scale), skew.skewMatrix);
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(id); // Signal a prefab change so it updates the render buffers
}
internal void UpdatePrefab(Block block, byte material, bool flipped)
@ -111,57 +137,58 @@ namespace TechbloxModdingAPI.Blocks.Engines
: uint.MaxValue;
if (prefabAssetID == uint.MaxValue)
{
if (entitiesDB.QueryEntityOptional<DBEntityStruct>(block)) //The block exists
if (entitiesDB.QueryEntityOptional<BlockTagEntityStruct>(block)) //The block exists
throw new BlockException("Prefab asset ID not found for block " + block); //Set by the game
return;
}
uint prefabId =
PrefabsID.GetOrCreatePrefabID((ushort) prefabAssetID, material, 1, flipped);
PrefabsID.GetOrAddPrefabID((ushort) prefabAssetID, material, 1, flipped);
entitiesDB.QueryEntityOrDefault<GFXPrefabEntityStructGPUI>(block).prefabID = prefabId;
if (block.Exists)
{
entitiesDB.PublishEntityChange<CubeMaterialStruct>(block.Id);
entitiesDB.PublishEntityChange<GFXPrefabEntityStructGPUI>(block.Id);
entitiesDB.PublishEntityChangeDelayed<CubeMaterialStruct>(block.Id);
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(block.Id);
ref BuildingActionComponent local =
ref entitiesDB.QueryEntity<BuildingActionComponent>(BuildingDroneUtility
.GetLocalBuildingDrone(entitiesDB).ToEGID(entitiesDB));
local.buildAction = BuildAction.ChangeMaterial;
local.targetPosition = block.Position;
this.entitiesDB.PublishEntityChange<BuildingActionComponent>(local.ID);
this.entitiesDB.PublishEntityChangeDelayed<BuildingActionComponent>(local.ID);
}
//Phyiscs prefab: prefabAssetID, set on block creation from the CubeListData
}
public void UpdateBlockColor(EGID id)
{
entitiesDB.PublishEntityChangeDelayed<ColourParameterEntityStruct>(id);
}
public bool BlockExists(EGID blockID)
{
return entitiesDB.Exists<DBEntityStruct>(blockID);
return entitiesDB.Exists<BlockTagEntityStruct>(blockID);
}
public SimBody[] GetSimBodiesFromID(byte id)
{
var ret = new FasterList<SimBody>(4);
var oide = entitiesDB.QueryEntities<ObjectIdEntityStruct>();
var oids = entitiesDB.QueryEntitiesOptional<ObjectIdEntityStruct>(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP);
EGIDMapper<GridConnectionsEntityStruct>? connections = null;
foreach (var ((oids, count), _) in oide)
foreach (var oid in oids)
{
for (int i = 0; i < count; i++)
{
ref ObjectIdEntityStruct oid = ref oids[i];
if (oid.objectId != id) continue;
if (oid.Get().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)
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)
goto DUPLICATE; //Multiple Object Identifiers on one rigid body
}
ret.Add(new SimBody(rid));
DUPLICATE: ;
}
DUPLICATE: ;*/
}
return ret.ToArray();
@ -169,14 +196,14 @@ namespace TechbloxModdingAPI.Blocks.Engines
public SimBody[] GetConnectedSimBodies(uint id)
{
var joints = entitiesDB.QueryEntities<JointEntityStruct>(MachineSimulationGroups.JOINTS_GROUP).ToBuffer();
var (joints, count) = entitiesDB.QueryEntities<JointEntityStruct>(MachineSimulationGroups.JOINTS_GROUP);
var list = new FasterList<SimBody>(4);
for (int i = 0; i < joints.count; i++)
for (int i = 0; i < count; i++)
{
ref var joint = ref joints.buffer[i];
ref var joint = ref joints[i];
if (joint.isBroken) continue;
if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB));
else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA));
/*if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB)); - TODO:
else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA));*/
}
return list.ToArray();
@ -186,14 +213,13 @@ namespace TechbloxModdingAPI.Blocks.Engines
{
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
var bodies = new HashSet<uint>();
foreach (var (coll, _) in groups)
foreach (var ((coll, count), _) in groups)
{
var array = coll.ToBuffer().buffer;
for (var index = 0; index < array.capacity; index++)
for (var index = 0; index < count; index++)
{
var conn = array[index];
if (conn.clusterId == cid)
bodies.Add(conn.machineRigidBodyId);
var conn = coll[index];
/*if (conn.clusterId == cid) - TODO
bodies.Add(conn.machineRigidBodyId);*/
}
}
@ -202,10 +228,10 @@ namespace TechbloxModdingAPI.Blocks.Engines
public EGID? FindBlockEGID(uint id)
{
var groups = entitiesDB.FindGroups<DBEntityStruct>();
var groups = entitiesDB.FindGroups<BlockTagEntityStruct>();
foreach (ExclusiveGroupStruct group in groups)
{
if (entitiesDB.Exists<DBEntityStruct>(id, group))
if (entitiesDB.Exists<BlockTagEntityStruct>(id, group))
return new EGID(id, group);
}
@ -215,15 +241,14 @@ namespace TechbloxModdingAPI.Blocks.Engines
public Cluster GetCluster(uint sbid)
{
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
foreach (var (coll, _) in groups)
foreach (var ((coll, count), _) in groups)
{
var array = coll.ToBuffer().buffer;
for (var index = 0; index < array.capacity; index++)
for (var index = 0; index < count; index++)
{
var conn = array[index];
var conn = coll[index];
//Static blocks don't have a cluster ID but the cluster destruction manager should have one
if (conn.machineRigidBodyId == sbid && conn.clusterId != uint.MaxValue)
return new Cluster(conn.clusterId);
/*if (conn.machineRigidBodyId == sbid && conn.clusterId != uint.MaxValue) - TODO:
return new Cluster(conn.clusterId);*/
}
}
@ -232,49 +257,36 @@ namespace TechbloxModdingAPI.Blocks.Engines
public Block[] GetBodyBlocks(uint sbid)
{
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
var groups = entitiesDB.FindGroups<GridConnectionsEntityStruct>();
groups = new QueryGroups(groups).Except(CommonExclusiveGroups.DISABLED_JOINTS_IN_SIM_GROUP).Evaluate().result;
var set = new HashSet<Block>();
foreach (var (coll, _) in groups)
foreach (var ((coll, tags, count), _) in entitiesDB.QueryEntities<GridConnectionsEntityStruct, BlockTagEntityStruct>(groups))
{
var array = coll.ToBuffer().buffer;
for (var index = 0; index < array.capacity; index++)
for (var index = 0; index < count; index++)
{
var conn = array[index];
if (conn.machineRigidBodyId == sbid)
set.Add(Block.New(conn.ID));
var conn = coll[index];
/*if (conn.machineRigidBodyId == sbid) - TODO
set.Add(Block.New(tags[index].ID));*/
}
}
return set.ToArray();
}
#if DEBUG
public EntitiesDB GetEntitiesDB()
public ObjectID[] GetObjectIDsFromID(byte id)
{
return entitiesDB;
}
#endif
if (!entitiesDB.HasAny<ObjectIDTweakableComponent>(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP))
return Array.Empty<ObjectID>();
[HarmonyPatch]
public static class RenderingPatch
var ret = new FasterList<ObjectID>(4);
var oids = entitiesDB.QueryEntitiesOptional<ObjectIDTweakableComponent>(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP);
foreach (var oid in oids)
{
private static ComputeRenderingEntitiesMatricesEngine Engine;
public static void Postfix(ComputeRenderingEntitiesMatricesEngine __instance)
{
Engine = __instance;
if (oid.Get().objectIDToTrigger == id)
ret.Add(new ObjectID(oid.EGID));
}
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));
}
return ret.ToArray();
}
}
}

View file

@ -26,18 +26,13 @@ namespace TechbloxModdingAPI.Blocks.Engines
public string Name { get; } = "TechbloxModdingAPIBlockEventsEngine";
public bool isRemovable { get; } = false;
private bool shouldAddRemove;
public void Add(ref BlockTagEntityStruct entityComponent, EGID egid)
{
if (!(shouldAddRemove = !shouldAddRemove))
return;
Placed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid});
}
public void Remove(ref BlockTagEntityStruct entityComponent, EGID egid)
{
if (!(shouldAddRemove = !shouldAddRemove))
return;
Removed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid});
}
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Reflection;
using Gamecraft.Blocks.BlockGroups;
@ -18,9 +18,10 @@ using Svelto.ECS.DataStructures;
using Svelto.ECS.EntityStructs;
using Svelto.ECS.Native;
using Svelto.ECS.Serialization;
using Techblox.Blocks;
using Techblox.Blocks.Connections;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
@ -48,7 +49,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
private static readonly MethodInfo SerializeGhostBlueprint =
AccessTools.Method(SerializeGhostBlueprintType, "SerializeClipboardGhostEntities");
private static NativeEntityRemove nativeRemove;
private static NativeEntityRemove nativeBlockRemove;
private static NativeEntityRemove nativeConnectionRemove;
private static MachineGraphConnectionEntityFactory connectionFactory;
private static IEntityFunctions entityFunctions;
private static ClipboardSerializationDataResourceManager clipboardManager;
@ -88,8 +90,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
public void RemoveBlockGroup(int id)
{
BlockGroupUtility.RemoveAllBlocksInBlockGroup(id, entitiesDB, removedConnections, nativeRemove,
connectionFactory, default).Complete();
BlockGroupUtility.RemoveAllBlocksInBlockGroup(id, entitiesDB, removedConnections, nativeBlockRemove,
nativeConnectionRemove, connectionFactory, default).Complete();
}
public int CreateBlockGroup(float3 position, quaternion rotation)
@ -173,7 +175,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
foreach (var block in blocks)
{
GhostChildUtility.BuildGhostChild(in playerID, block.Id, in pos, in rot, entitiesDB,
BuildGhostBlueprintFactory, false, bssesopt.Get().buildingDroneReference);
BuildGhostBlueprintFactory, false, bssesopt.Get().buildingDroneReference,
FullGameFields._managers.blockLabelResourceManager);
}
}
@ -272,10 +275,11 @@ namespace TechbloxModdingAPI.Blocks.Engines
uint ghostChildBlockId = CommonExclusiveGroups.GetNewGhostChildBlockID();
var ghostEntityReference = GhostBlockUtils.GetGhostEntityReference(sourceId.entityID, entitiesDB);
var entityInitializer = BuildGhostBlueprintFactory.Build(
new EGID(ghostChildBlockId, BoxSelectExclusiveGroups.GhostChildEntitiesExclusiveGroup), /*dbStruct.DBID*/ (uint)BlockIDs.Cube);
new EGID(ghostChildBlockId, BoxSelectExclusiveGroups.GhostChildEntitiesExclusiveGroup), /*dbStruct.DBID*/ (uint)BlockIDs.Cube,
FullGameFields._managers.blockLabelResourceManager);
entityInitializer.Init(dbStruct);
entityInitializer.Init(new GFXPrefabEntityStructGPUI(
PrefabsID.GetOrCreatePrefabID((ushort)entityInitializer.Get<PrefabAssetIDComponent>().prefabAssetID,
PrefabsID.GetOrAddPrefabID((ushort)entityInitializer.Get<PrefabAssetIDComponent>().prefabAssetID,
entitiesDB.QueryEntity<CubeMaterialStruct>(sourceId).materialId, 7,
FlippedBlockUtils.IsFlipped(in scalingEntityStruct.scale)), true));
entityInitializer.Init(entitiesDB.QueryEntity<CubeMaterialStruct>(sourceId));
@ -337,7 +341,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
public static void Prefix(IEntityFunctions entityFunctions,
MachineGraphConnectionEntityFactory machineGraphConnectionEntityFactory)
{
nativeRemove = entityFunctions.ToNativeRemove<BlockEntityDescriptor>("GCAPI" + nameof(BlueprintEngine));
nativeBlockRemove = entityFunctions.ToNativeRemove<BlockEntityDescriptor>("TBAPI" + nameof(BlueprintEngine));
nativeConnectionRemove = entityFunctions.ToNativeRemove<MachineConnectionEntityDescriptor>("TBAPI" + nameof(BlueprintEngine));
connectionFactory = machineGraphConnectionEntityFactory;
BlueprintEngine.entityFunctions = entityFunctions;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,50 @@
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
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).servoVelocity;
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).servoVelocity;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).servoVelocity = value;
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).servoVelocity = value;
}
}
@ -45,11 +45,11 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).minDeviation;
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).minDeviation;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).minDeviation = value;
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).minDeviation = value;
}
}
@ -60,11 +60,11 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).maxDeviation;
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).maxDeviation;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).maxDeviation = value;
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).maxDeviation = value;
}
}
@ -75,11 +75,11 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).reverse;
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).reverse;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).reverse = value;
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).reverse = value;
}
}
@ -90,11 +90,11 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).hasProportionalInput;
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).hasProportionalInput;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).hasProportionalInput = value;
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).hasProportionalInput = value;
}
}
@ -105,11 +105,11 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).directionVector;
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).directionVector;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).directionVector = value;
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).directionVector = value;
}
}
@ -120,11 +120,11 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).rotationAxis;
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).rotationAxis;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).rotationAxis = value;
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).rotationAxis = value;
}
}
@ -135,11 +135,11 @@ namespace TechbloxModdingAPI.Blocks
{
get
{
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).forceAxis;
return BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).forceAxis;
}
set
{
BlockEngine.GetBlockInfo<RobocraftX.Blocks.ServoReadOnlyStruct>(this).forceAxis = value;
BlockEngine.GetBlockInfo<Techblox.ServoBlocksServer.ServoReadOnlyTweakableComponent>(this).forceAxis = value;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,34 +0,0 @@
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,6 +62,14 @@ namespace TechbloxModdingAPI
var id = initializer(this);
if (!dict.ContainsKey(id)) // Multiple instances may be created
dict.Add(id, this);
else
{
Logging.MetaDebugLog($"An object of this type and ID is already stored: {GetType()} - {id}");
Logging.MetaDebugLog(this);
Logging.MetaDebugLog(dict[id]);
}
Id = id;
}
#region ECS initializer stuff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,6 +20,22 @@ namespace TechbloxModdingAPI
add => seatExited += value;
remove => seatExited -= value;
}
internal static WrappedHandler<PlayerEventArgs> joined;
public static event EventHandler<PlayerEventArgs> Joined
{
add => joined += value;
remove => joined -= value;
}
internal static WrappedHandler<PlayerEventArgs> left;
public static event EventHandler<PlayerEventArgs> Left
{
add => left += value;
remove => left -= value;
}
}
public struct PlayerSeatEventArgs
@ -27,4 +43,10 @@ namespace TechbloxModdingAPI
public EGID SeatId;
public Seat Seat => (Seat)Block.New(SeatId);
}
public struct PlayerEventArgs
{
public EGID PlayerId;
public Player Player => Player.GetInstance(PlayerId.entityID);
}
}

View file

@ -10,6 +10,7 @@ using RobocraftX.Physics;
using Svelto.ECS;
using Techblox.BuildingDrone;
using Techblox.Camera;
using Techblox.Character;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility;
@ -23,8 +24,8 @@ namespace TechbloxModdingAPI
public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID>
{
// static functionality
private static PlayerEngine playerEngine = new PlayerEngine();
private static PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine();
private static readonly PlayerEngine playerEngine = new PlayerEngine();
private static readonly PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine();
private static Player localPlayer;
/// <summary>
@ -64,18 +65,27 @@ namespace TechbloxModdingAPI
}
/// <summary>
/// Returns the current player belonging to this client.
/// Returns the current player belonging to this client. It will be different after entering/leaving simulation.
/// May return null if the local player doesn't exist.
/// </summary>
public static Player LocalPlayer
{
get
{
if (localPlayer == null || localPlayer.Id != playerEngine.GetLocalPlayer())
localPlayer = new Player(PlayerType.Local);
var playerId = playerEngine.GetLocalPlayer();
if (playerId == uint.MaxValue) return null;
if (localPlayer == null || localPlayer.Id != playerId)
localPlayer = GetInstance(playerId);
return localPlayer;
}
}
internal static Player GetInstance(uint id)
{
return EcsObjectBase.GetInstance(new EGID(id, CharacterExclusiveGroups.OnFootGroup),
e => new Player(e.entityID));
}
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary>
@ -119,6 +129,7 @@ namespace TechbloxModdingAPI
})
{
this.Type = player;
Id = base.Id.entityID;
}
// object fields & properties
@ -184,10 +195,8 @@ namespace TechbloxModdingAPI
/// The player's mass.
/// </summary>
/// <value>The mass.</value>
public float Mass =>
1f / playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().physicsMass.InverseMass;
private float _ping = -1f;
[Obsolete] // We cannot get it clientside or something
public float Mass => 0;
/// <summary>
/// The player's latest network ping time.
@ -197,12 +206,7 @@ namespace TechbloxModdingAPI
{
get
{
var opt = playerEngine.GetPlayerStruct<PlayerNetworkStatsEntityStruct>(Id, Type);
if (opt)
{
_ping = opt.Get().lastPingTimeSinceLevelLoad ?? _ping;
}
return _ping;
return playerEngine.GetPing() / 1000f;
}
}
@ -210,15 +214,16 @@ namespace TechbloxModdingAPI
/// The player's initial health when entering Simulation (aka Time Running) mode.
/// </summary>
/// <value>The initial health.</value>
[Obsolete("We can no longer get initial health, returns max health.")]
public float InitialHealth
{
get
{
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
return opt ? opt.Get().initialHealth : -1f;
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id);
return opt ? opt.Get().maxHealth : -1f;
}
set => playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get().initialHealth = value;
set => playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id).Get().maxHealth = value;
}
/// <summary>
@ -229,30 +234,25 @@ namespace TechbloxModdingAPI
{
get
{
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id);
return opt ? opt.Get().currentHealth : -1f;
}
set => playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get().currentHealth = value;
set => playerEngine.GetCharacterStruct<CharacterHealthEntityComponent>(Id).Get().currentHealth = value;
}
/// <summary>
/// Whether this <see cref="T:TechbloxModdingAPI.Player"/> is damageable.
/// </summary>
/// <value><c>true</c> if damageable; otherwise, <c>false</c>.</value>
[Obsolete("Players are probably always damageable")]
public bool Damageable
{
get
{
var opt = playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id);
return opt.Get().canTakeDamageStat;
}
get => true;
// ReSharper disable once ValueParameterNotUsed
set
{
ref var healthStruct = ref playerEngine.GetCharacterStruct<CharacterHealthEntityStruct>(Id).Get();
healthStruct.canTakeDamage = value;
healthStruct.canTakeDamageStat = value;
}
}
@ -260,30 +260,26 @@ namespace TechbloxModdingAPI
/// The player's lives when initially entering Simulation (aka Time Running) mode.
/// </summary>
/// <value>The initial lives.</value>
[Obsolete("The player has infinite lives")]
public uint InitialLives
{
get
{
var opt = playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id);
return opt ? opt.Get().initialLives : uint.MaxValue;
}
get => uint.MaxValue;
set => playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id).Get().initialLives = value;
// ReSharper disable once ValueParameterNotUsed
set { }
}
/// <summary>
/// The player's current lives in Simulation (aka Time Running) mode.
/// </summary>
/// <value>The current lives.</value>
[Obsolete("The player has infinite lives")]
public uint CurrentLives
{
get
{
var opt = playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id);
return opt ? opt.Get().currentLives : uint.MaxValue;
}
get => uint.MaxValue;
set => playerEngine.GetCharacterStruct<CharacterLivesEntityComponent>(Id).Get().currentLives = value;
// ReSharper disable once ValueParameterNotUsed
set { }
}
/*/// <summary>
@ -363,6 +359,18 @@ namespace TechbloxModdingAPI
public PlayerBuildingMode BuildingMode => (PlayerBuildingMode)Math.Log((double)playerEngine
.GetCharacterStruct<TimeStoppedModeComponent>(Id).Get().timeStoppedContext, 2); // It's a bit field in game now
public PlayerState State =>
playerEngine.GetCharacterStruct<CharacterTagEntityStruct>(Id).Get().ID.groupID switch
{
var group when group == CharacterExclusiveGroups.MachineSpawningGroup => PlayerState.HoldingMachine,
var group when group == CharacterExclusiveGroups.OnFootGroup => PlayerState.OnFoot,
var group when group == CharacterExclusiveGroups.InPilotSeatGroup => PlayerState.InSeat,
var group when group == CharacterExclusiveGroups.DyingOnFootGroup => PlayerState.OnFoot,
var group when group == CharacterExclusiveGroups.DyingInPilotSeatGroup => PlayerState.InSeat,
var group when group == CharacterExclusiveGroups.DeadGroup => PlayerState.OnFoot,
_ => throw new ArgumentOutOfRangeException("", "Unknown player state")
};
/// <summary>
/// Whether the player is sprinting.
/// </summary>
@ -435,16 +443,39 @@ namespace TechbloxModdingAPI
playerEngine.SetLocation(Id, location, exitSeat: exitSeat);
}
/// <summary>
/// Enter the given seat.
/// </summary>
/// <param name="seat">The seat to enter.</param>
public void EnterSeat(Seat seat)
{
playerEngine.EnterSeat(Id, seat.Id);
}
/// <summary>
/// Exit the seat the player is currently in.
/// </summary>
public void ExitSeat()
{
playerEngine.ExitSeat(Id);
}
/// <summary>
/// Spawn the machine the player is holding in time running mode.
/// </summary>
public bool SpawnMachine()
{
return playerEngine.SpawnMachine(Id);
}
/// <summary>
/// Despawn the player's machine in time running mode and place it in their hand.
/// </summary>
public bool DespawnMachine()
{
return playerEngine.DespawnMachine(Id);
}
/// <summary>
/// Returns the block the player is currently looking at in build mode.
/// </summary>
@ -481,7 +512,7 @@ namespace TechbloxModdingAPI
{
var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
return egid != default && egid.groupID == WiresGUIExclusiveGroups.WireGroup
? EcsObjectBase.GetInstance(new EGID(egid.entityID, NamedExclusiveGroup<WiresGroup>.Group),
? EcsObjectBase.GetInstance(new EGID(egid.entityID, BuildModeWiresGroups.WiresGroup.Group),
e => new Wire(e))
: null;
}
@ -495,6 +526,15 @@ namespace TechbloxModdingAPI
return playerEngine.GetSelectedBlocks(Id);
}
/// <summary>
/// Returns the ghost block that shows the block to be placed in build mode.
/// </summary>
/// <returns>A block instance or null if not found</returns>
public Block GetGhostBlock()
{
return playerEngine.GetGhostBlock(Id);
}
public bool Equals(Player other)
{
if (ReferenceEquals(null, other)) return false;

View file

@ -8,18 +8,20 @@ using RobocraftX.Common.Input;
using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.Physics;
using RobocraftX.Blocks.Ghost;
using Gamecraft.GUI.HUDFeedbackBlocks;
using RobocraftX.Blocks;
using RobocraftX.PilotSeat;
using RobocraftX.Common;
using RobocraftX.Multiplayer;
using RobocraftX.SimulationModeState;
using Svelto.ECS;
using Techblox.Camera;
using Unity.Mathematics;
using Svelto.ECS.DataStructures;
using Svelto.ECS.EntityStructs;
using Techblox.BuildingDrone;
using Techblox.Character;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Players
{
@ -48,10 +50,10 @@ namespace TechbloxModdingAPI.Players
public uint GetLocalPlayer()
{
if (!isReady) return uint.MaxValue;
var localPlayers = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers).ToBuffer();
if (localPlayers.count > 0)
var (localPlayers, count) = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers);
if (count > 0)
{
return localPlayers.buffer[0].ID.entityID;
return localPlayers[0].ID.entityID;
}
return uint.MaxValue;
}
@ -59,10 +61,10 @@ namespace TechbloxModdingAPI.Players
public uint GetRemotePlayer()
{
if (!isReady) return uint.MaxValue;
var localPlayers = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers).ToBuffer();
if (localPlayers.count > 0)
var (localPlayers, count) = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers);
if (count > 0)
{
return localPlayers.buffer[0].ID.entityID;
return localPlayers[0].ID.entityID;
}
return uint.MaxValue;
}
@ -111,18 +113,10 @@ namespace TechbloxModdingAPI.Players
return true;
}
public bool GetGameOverScreen(uint playerId)
{
if (entitiesDB == null) return false;
ref HudActivatedBlocksEntityStruct habes = ref entitiesDB.QueryEntity<HudActivatedBlocksEntityStruct>(HUDFeedbackBlocksGUIExclusiveGroups.GameOverHudEgid);
NativeDynamicArrayCast<EGID> nativeDynamicArrayCast = new NativeDynamicArrayCast<EGID>(habes.activatedBlocksOrdered);
return nativeDynamicArrayCast.count > 0;
}
public bool IsDead(uint playerId)
{
if (entitiesDB == null) return true;
return entitiesDB.Exists<RigidBodyEntityStruct>(playerId, CharacterExclusiveGroups.DeadCharacters);
return entitiesDB.Exists<RigidBodyEntityStruct>(playerId, CharacterExclusiveGroups.DeadGroup);
}
// reusable methods
@ -171,8 +165,9 @@ namespace TechbloxModdingAPI.Players
var opt = GetCameraStruct<PhysicCameraRayCastEntityStruct>(playerId);
if (!opt) return default;
PhysicCameraRayCastEntityStruct rayCast = opt;
EGID physicCameraEgid = new EGID(playerId, CameraExclusiveGroups.PhysicCameraGroup);
float distance = maxDistance < 0
? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast,
? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast, physicCameraEgid,
GhostBlockUtils.GhostCastMethod.GhostCastProportionalToBlockSize)
: maxDistance;
if (rayCast.hit && rayCast.distance <= distance)
@ -204,9 +199,11 @@ namespace TechbloxModdingAPI.Players
public void EnterSeat(uint playerId, EGID seatId)
{
PilotSeatGroupUtils.SwapTagTo<OCCUPIED_TAG>(Functions, seatId);
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB))
return;
/*PilotSeatGroupUtils.SwapTagTo<OCCUPIED_TAG>(Functions, seatId);
var opt = GetCharacterStruct<CharacterPilotSeatEntityStruct>(playerId, out var group);
if (!opt) return;
if (!opt) return; - TODO: This is server code and mods run in client code atm. We can only send inputs even in singleplayer as it is.
ref CharacterPilotSeatEntityStruct charSeat = ref opt.Get();
var charId = new EGID(playerId, group);
charSeat.pilotSeatEntity = entitiesDB.GetEntityReference(seatId);
@ -216,16 +213,62 @@ namespace TechbloxModdingAPI.Players
ref var seat = ref entitiesDB.QueryEntity<PilotSeatEntityStruct>(seatId);
seat.occupyingCharacter = entitiesDB.GetEntityReference(charId);
charSeat.followCam = entitiesDB.QueryEntity<SeatFollowCamComponent>(seatId).followCam;
Functions.SwapEntityGroup<CharacterEntityDescriptor>(charId, CharacterExclusiveGroups.InPilotSeatGroup);
Functions.SwapEntityGroup<CharacterEntityDescriptor>(charId, CharacterExclusiveGroups.InPilotSeatGroup);*/
}
public void ExitSeat(uint playerId)
{
EGID egid = new EGID(playerId, CharacterExclusiveGroups.InPilotSeatGroup);
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB))
return;
/*EGID egid = new EGID(playerId, CharacterExclusiveGroups.InPilotSeatGroup);
var opt = entitiesDB.QueryEntityOptional<CharacterPilotSeatEntityStruct>(egid);
if (!opt) return;
opt.Get().instantExit = true;
entitiesDB.PublishEntityChange<CharacterPilotSeatEntityStruct>(egid);
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,11 +1,14 @@
using System;
using RobocraftX.Character;
using RobocraftX.Character.Movement;
using RobocraftX.Common.Input;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Players
{
public class PlayerEventsEngine : IApiEngine, IReactOnSwap<CharacterPilotSeatEntityStruct>
public class PlayerEventsEngine : IApiEngine, IReactOnSwap<CharacterPilotSeatEntityStruct>, IReactOnAddAndRemove<PlayerIDStruct>
{
public void Ready()
{
@ -21,13 +24,22 @@ namespace TechbloxModdingAPI.Players
public void MovedTo(ref CharacterPilotSeatEntityStruct entityComponent, ExclusiveGroupStruct previousGroup, EGID egid)
{
var seatId = entityComponent.pilotSeatEntity.ToEGID(entitiesDB);
var player = EcsObjectBase.GetInstance(new EGID(egid.entityID, CharacterExclusiveGroups.OnFootGroup),
e => new Player(e.entityID));
entitiesDB.TryGetEGID(entityComponent.pilotSeatEntity, out var seatId); //TODO: Can't get EGID
var player = Player.GetInstance(egid.entityID);
if (previousGroup == CharacterExclusiveGroups.InPilotSeatGroup)
player.seatExited.Invoke(this, new PlayerSeatEventArgs { SeatId = seatId});
else if (egid.groupID == CharacterExclusiveGroups.InPilotSeatGroup)
player.seatEntered.Invoke(this, new PlayerSeatEventArgs { SeatId = seatId });
}
public void Add(ref PlayerIDStruct entityComponent, EGID egid)
{
Player.joined.Invoke(this, new PlayerEventArgs { PlayerId = egid });
}
public void Remove(ref PlayerIDStruct entityComponent, EGID egid)
{
Player.left.Invoke(this, new PlayerEventArgs { PlayerId = egid });
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,62 +1,23 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using TechbloxModdingAPI.App;
using BepInEx;
using BepInEx.Bootstrap;
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.App;
using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Tests
{
#if DEBUG
// unused by design
/// <summary>
/// Modding API implemented as a standalone IPA Plugin.
/// Ideally, TechbloxModdingAPI should be loaded by another mod; not itself
/// </summary>
class TechbloxModdingAPIPluginTest : IllusionPlugin.IEnhancedPlugin
#if DEBUG // The API should be loaded by other plugins, but it can be used by itself for testing
[BepInPlugin("org.exmods.TechbloxModdingAPIPluginTest", PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
[BepInProcess("Techblox.exe")]
public class TechbloxModdingAPIPluginTest : BaseUnityPlugin
{
private static Harmony harmony { get; set; }
public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
public string HarmonyID { get; } = "org.git.exmods.modtainers.techbloxmoddingapi";
public override void OnApplicationQuit()
private void Awake()
{
Main.Shutdown();
}
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) =>
@ -67,9 +28,6 @@ namespace TechbloxModdingAPI.Tests
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")
@ -82,291 +40,30 @@ namespace TechbloxModdingAPI.Tests
.Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
.Build();
CommandBuilder.Builder()
.Name("MoveLastBlock")
.Description("Move the most-recently-placed block, and any connected blocks by the given offset")
.Action((float x, float y, float z) =>
{
if (GameState.IsBuildMode())
foreach (var block in Block.GetLastPlacedBlock().GetConnectedCubes())
block.Position += new Unity.Mathematics.float3(x, y, z);
else
Logging.CommandLogError("Blocks can only be moved in Build mode!");
}).Build();
CommandBuilder.Builder()
.Name("PlaceAluminium")
.Description("Place a block of aluminium at the given coordinates")
.Action((float x, float y, float z) =>
{
var block = Block.PlaceNew(BlockIDs.Cube, new float3(x, y, z));
Logging.CommandLog("Block placed with type: " + block.Type);
})
.Build();
CommandBuilder.Builder()
.Name("PlaceAluminiumLots")
.Description("Place a lot of blocks of aluminium at the given coordinates")
.Action((float x, float y, float z) =>
{
Logging.CommandLog("Starting...");
var sw = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
for (int j = 0; j < 100; j++)
Block.PlaceNew(BlockIDs.Cube, new float3(x + i, y, z + j));
sw.Stop();
Logging.CommandLog("Finished in " + sw.ElapsedMilliseconds + "ms");
})
.Build();
Block b = null;
CommandBuilder.Builder("moveBlockInSim", "Run in build mode first while looking at a block, then in sim to move it up")
.Action(() =>
{
if (b == null)
{
b = new Player(PlayerType.Local).GetBlockLookedAt();
Logging.CommandLog("Block saved: " + b);
}
else
Logging.CommandLog("Block moved to: " + (b.GetSimBody().Position += new float3(0, 2, 0)));
}).Build();
CommandBuilder.Builder("Error", "Throw an error to make sure SimpleCustomCommandEngine's wrapper catches it.")
.Action(() => { throw new Exception("Error Command always throws an error"); })
.Build();
CommandBuilder.Builder("ColorBlock",
"Change color of the block looked at if there's any.")
.Action<string>(str =>
{
if (!Enum.TryParse(str, out BlockColors color))
{
Logging.CommandLog("Color " + str + " not found! Interpreting as 4 color values.");
var s = str.Split(' ');
new Player(PlayerType.Local).GetBlockLookedAt().CustomColor = new float4(float.Parse(s[0]),
float.Parse(s[1]), float.Parse(s[2]), float.Parse(s[3]));
return;
}
new Player(PlayerType.Local).GetBlockLookedAt().Color = color;
Logging.CommandLog("Colored block to " + color);
}).Build();
CommandBuilder.Builder("MoveBlockByID", "Gets a block based on its object identifier and teleports it up.")
.Action<char>(ch =>
{
foreach (var body in SimBody.GetFromObjectID(ch))
{
Logging.CommandLog("SimBody: " + body);
body.Position += new float3(0, 10, 0);
foreach (var bodyConnectedBody in body.GetConnectedBodies())
{
Logging.CommandLog("Moving " + bodyConnectedBody);
bodyConnectedBody.Position += new float3(0, 10, 0);
}
}
}).Build();
CommandBuilder.Builder("TestChunkHealth", "Sets the chunk looked at to the given health.")
.Action((float val, float max) =>
{
var body = new Player(PlayerType.Local).GetSimBodyLookedAt();
if (body == null) return;
body.CurrentHealth = val;
body.InitialHealth = max;
Logging.CommandLog("Health set to: " + val);
}).Build();
CommandBuilder.Builder("placeBlockGroup", "Places some blocks in a group")
.Action((float x, float y, float z) =>
{
var pos = new float3(x, y, z);
var group = BlockGroup.Create(new Block(BlockIDs.Cube, pos) {Color = BlockColors.Aqua});
new Block(BlockIDs.Cube, pos += new float3(1, 0, 0))
{Color = BlockColors.Blue, BlockGroup = group};
new Block(BlockIDs.Cube, pos += new float3(1, 0, 0))
{Color = BlockColors.Green, BlockGroup = group};
new Block(BlockIDs.Cube, pos + new float3(1, 0, 0))
{Color = BlockColors.Lime, BlockGroup = group};
}).Build();
CommandBuilder.Builder("placeCustomBlock", "Places a custom block, needs a custom catalog and assets.")
.Action((float x, float y, float z) =>
{
Logging.CommandLog("Block placed: " +
Block.PlaceNew((BlockIDs) 500, new float3(0, 0, 0)));
}).Build();
CommandBuilder.Builder("toggleTimeMode", "Enters or exits simulation.")
.Action((float x, float y, float z) =>
{
Game.CurrentGame().ToggleTimeMode();
}).Build();
CommandBuilder.Builder("testColorBlock", "Tests coloring a block to default color")
.Action(() => Player.LocalPlayer.GetBlockLookedAt().Color = BlockColors.Default).Build();
CommandBuilder.Builder("testMaterialBlock", "Tests materialing a block to default material")
.Action(() => Player.LocalPlayer.GetBlockLookedAt().Material = BlockMaterial.Default).Build();
CommandBuilder.Builder("testGameName", "Tests changing the game name")
.Action(() => Game.CurrentGame().Name = "Test").Build();
CommandBuilder.Builder("makeBlockStatic", "Makes a block you look at static")
.Action(() => Player.LocalPlayer.GetBlockLookedAt().Static = true).Build();
Game.AddPersistentDebugInfo("InstalledMods", InstalledMods);
/*Block.Placed += (sender, args) =>
Logging.MetaDebugLog("Placed block " + args.Block);
Block.Removed += (sender, args) =>
Logging.MetaDebugLog("Removed block " + args.Block);*/
}
// 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);*/
// Plugin startup logic
Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!");
/*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 void OnDestroy()
{
Main.Shutdown();
}
private string modsString;
private string InstalledMods()
{
if (modsString != null) return modsString;
StringBuilder sb = new StringBuilder("Installed mods:");
foreach (var plugin in PluginManager.Plugins)
sb.Append("\n" + plugin.Name + " - " + plugin.Version);
foreach (var (_, plugin) in Chainloader.PluginInfos)
sb.Append("\n" + plugin.Metadata.Name + " - " + plugin.Metadata.Version);
return modsString = sb.ToString();
}
public override void OnUpdate()
{
if (UnityEngine.Input.GetKeyDown(KeyCode.End))
{
Console.WriteLine("Pressed button to toggle console");
FakeInput.CustomInput(new LocalCosmeticInputEntityComponent {commandLineToggleInput = true});
}
}
[HarmonyPatch]
@ -383,6 +80,5 @@ namespace TechbloxModdingAPI.Tests
return ((Func<bool>) MinimumSpecsCheck.CheckRequirementsMet).Method;
}
}
}
#endif
#endif
}

View file

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

View file

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

View file

@ -1,7 +1,7 @@
using Svelto.ECS;
using Svelto.ECS.Hybrid;
namespace TechbloxModdingAPI.Utility
namespace TechbloxModdingAPI.Utility.ECS
{
public static class ManagedApiExtensions
{
@ -29,7 +29,8 @@ namespace TechbloxModdingAPI.Utility
/// <param name="group">The group of the entity if the object can have multiple</param>
/// <typeparam name="T">The component to query</typeparam>
/// <returns>A reference to the component or a dummy value</returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj, ExclusiveGroupStruct group = default)
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj,
ExclusiveGroupStruct group = default)
where T : struct, IEntityViewComponent
{
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
@ -45,14 +46,30 @@ namespace TechbloxModdingAPI.Utility
/// <param name="group">The group of the entity if the object can have multiple</param>
/// <typeparam name="T">The component to query</typeparam>
/// <returns>A reference to the component or a dummy value</returns>
public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj, ExclusiveGroupStruct group = default)
public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj,
ExclusiveGroupStruct group = default)
where T : struct, IEntityViewComponent
{
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
var opt = QueryEntityOptional<T>(entitiesDB, id);
if (opt) return ref opt.Get();
if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrCreate<T>();
if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrAdd<T>();
return ref opt.Get(); //Default value
}
/// <summary>
/// Query entities as OptionalRefs. The elements always exist, it's just a nice way to encapsulate the data.
/// </summary>
/// <param name="entitiesDB"></param>
/// <param name="group"></param>
/// <param name="select"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TR"></typeparam>
/// <returns></returns>
public static RefCollection<T> QueryEntitiesOptional<T>(this EntitiesDB entitiesDB, ExclusiveGroupStruct group) where T : struct, IEntityViewComponent
{
var (buffer, ids, count) = entitiesDB.QueryEntities<T>(group);
return new RefCollection<T>(count, buffer, ids, group);
}
}
}

View file

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

View file

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

View file

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RobocraftX.StateSync;
using Svelto.ECS;

View file

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

View file

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

View file

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

View file

@ -38,7 +38,7 @@ PROJECT_NAME = "TechbloxModdingAPI"
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = "v2.1.0"
PROJECT_NUMBER = "v2.2.0"
# 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