Compare commits

...

83 commits
v2.0 ... master

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
5c1fe34f46 Bump version and restore displayed block fix attempt
It doesn't work but anyway
Also remove parameter that allowed placing blocks in sim
2021-12-15 02:15:24 +01:00
ef1b3de1a1 Remove preview from references 2021-12-15 00:16:51 +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
e3a7961be4 Made the Game.Enter event only fire once loading finishes and fixed player building mode
Also attempted to fix material changing and updating the rendered block
2021-11-25 01:48:06 +01:00
f53d0b63e7 Fix issues uncovered by the tests
- Fixed the game enter API
- Fixed ToggleTimeMode() placing the player underground by using the full switch animation
- Fixed the menu enter/exit events only firing once due to the game keeping the menu loaded
- Moved everything from AppEngine to GameMenuEngine for consistency
- Reimplemented the Block.Static property, it should actually work again too
- Made the block property test only use blocks placed during the previous test, improved error handling and temporarily disabled testing the Material and the Flipped properties as they cause the game to crash
2021-11-06 04:10:00 +01:00
619a5003cf Update to Techblox 2021.11.03.15.56
Save game details were changed, they may not work properly
Game mode change event no longer sends game data, needs fixing
2021-11-04 20:45:21 +01:00
6204b226d1 Seat events, and everything needed to get there
- Added support for seat enter and exit events and a test for them
- Added support for entering and exiting seat from code
- Changed the Id property of ECS objects to non-abstract, requiring it in the constructor, so that the Player class can inherit EcsObjectBase
- Added a weird constructor to EcsObjectBase that allows running code to determine the object Id
- Added interface for engines that receive entity functions
- Exposed the entity submission scheduler and removed it from FullGameFields because it moved from there
- Made the Game.Enter event only fire after the first entity submission so the game is fully initialized and the local player exists
- Added all seat groups to the dictionary
2021-10-11 01:26:35 +02:00
4bd636b8ed Add wrapped event handler, using the existing ECS object instances
- Added a wrapper class that handles the individual wrapping of event handlers to individually handle exceptions - now it tracks the wrapped event handlers so it can unregister them properly
- Fixed an exception that happened when two ECS objects were created with the same EGID
- Added support for returning an existing ECS object if it exists instead of always creating a new one
- Added a parameter to the entity query extension methods to override the group of the ECS object (could be used for the player properties)
2021-10-08 03:58:01 +02:00
8a03277d84 Added block placement in sim and ECS object tracking
ECS objects are stored in a newly created weak dictionary so that events can be called on them and possibly other things can be done with them
2021-10-02 03:50:20 +02:00
aa947eaba1 Update to Techblox 2021.09.27.15.17
Fixed block name print regex
Made Game.WorkshopId obsolete as it's removed from the game
Fixed removing blocks
2021-10-02 00:01:47 +02:00
63295f82c9 Update to Techblox 2021.09.03.10.36
Removed old dependencies, including uREPL
Added new block IDs
Implemented basic command handling to support existing mod commands
2021-09-07 23:15:03 +02:00
033ebdb86d Fix looking at wires, reduce Wire code
Also added two port name properties directly to the wires
Also added support for converting OptionalRefs to Nullables
2021-09-03 01:30:38 +02:00
2513040343 Add code generator and new block classes
Block classes are generated with all of the properties from the given structs
Properties can be renamed, their name is pulled from TweakableStat attributes if possible
Added support for getting the wire being looked at
2021-08-12 01:11:02 +02:00
77d5e59ef6 Add Motor class 2021-08-12 00:44:23 +02:00
9693341d7a Add block types, run tests, remove unintended properties 2021-08-12 00:34:39 +02:00
c0eae77421 Finish code generator (mostly)
Made the generated members public and final
Removed the comment from the start of the files
Generating the files directly into the project folder
Added comments describing the generated constructors and properties
Using SignalingBlock as a base class
Added support for renaming properties
Added support for getting the name from the tweakable stat name
Added/updated functional block classes
2021-08-11 23:44:26 +02:00
3351993936 Automatically generate properties, fixes, engine class 2021-07-29 01:04:27 +02:00
49c3b60963 Get wire looked at, block class generation 2021-07-29 00:08:57 +02:00
ece71c45a6 Update to Techblox 2021.07.21.16.17 2021-07-22 22:20:53 +02:00
2a1676ce0f Update block ID list 2021-07-01 15:41:58 +02: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
99 changed files with 8264 additions and 2446 deletions

View file

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

View file

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

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

File diff suppressed because it is too large Load diff

53
CodeGenerator/Program.cs Normal file
View file

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

View file

@ -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

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

View file

@ -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,62 +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 event EventHandler<MenuEventArgs> EnterMenu;
public event EventHandler<MenuEventArgs> ExitMenu;
public IEntityFactory Factory { set; private get; }
public string Name => "TechbloxModdingAPIAppEngine";
public bool isRemovable => false;
public EntitiesDB entitiesDB { set; private get; }
public void Dispose()
{
IsInMenu = false;
ExceptionUtil.InvokeEvent(ExitMenu, this, new MenuEventArgs { });
}
public void Ready()
{
IsInMenu = true;
ExceptionUtil.InvokeEvent(EnterMenu, this, new MenuEventArgs { });
}
// app functionality
public bool IsInMenu
{
get;
private set;
} = false;
public Game[] GetMyGames()
{
EntityCollection<MyGameDataEntityStruct> mgsevs = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
var mgsevsB = mgsevs.ToBuffer().buffer;
Game[] games = new Game[mgsevs.count];
for (int i = 0; i < mgsevs.count; i++)
{
Utility.Logging.MetaDebugLog($"Found game named {mgsevsB[i].GameName}");
games[i] = new Game(mgsevsB[i].ID);
}
return games;
}
}
public struct MenuEventArgs
{
}
}

View file

@ -14,22 +14,19 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public class Client public class Client
{ {
// extensible engine public static Client Instance { get; } = new Client();
protected static AppEngine appEngine = new AppEngine();
protected static Func<object> ErrorHandlerInstanceGetter; protected static Func<object> ErrorHandlerInstanceGetter;
protected static Action<object, Error> EnqueueError; protected static Action<object, Error> EnqueueError;
protected static Action<object> HandleErrorClosed;
/// <summary> /// <summary>
/// An event that fires whenever the main menu is loaded. /// An event that fires whenever the main menu is loaded.
/// </summary> /// </summary>
public static event EventHandler<MenuEventArgs> EnterMenu public static event EventHandler<MenuEventArgs> EnterMenu
{ {
add => appEngine.EnterMenu += ExceptionUtil.WrapHandler(value); add => Game.menuEngine.EnterMenu += value;
remove => appEngine.EnterMenu -= value; remove => Game.menuEngine.EnterMenu -= value;
} }
/// <summary> /// <summary>
@ -37,8 +34,8 @@ namespace TechbloxModdingAPI.App
/// </summary> /// </summary>
public static event EventHandler<MenuEventArgs> ExitMenu public static event EventHandler<MenuEventArgs> ExitMenu
{ {
add => appEngine.ExitMenu += ExceptionUtil.WrapHandler(value); add => Game.menuEngine.ExitMenu += value;
remove => appEngine.ExitMenu -= value; remove => Game.menuEngine.ExitMenu -= value;
} }
/// <summary> /// <summary>
@ -69,8 +66,8 @@ namespace TechbloxModdingAPI.App
{ {
get get
{ {
if (!appEngine.IsInMenu) return new Game[0]; if (!Game.menuEngine.IsInMenu) return Array.Empty<Game>();
return appEngine.GetMyGames(); return Game.menuEngine.GetMyGames();
} }
} }
@ -80,7 +77,7 @@ namespace TechbloxModdingAPI.App
/// <value><c>true</c> if in menu; <c>false</c> when loading or in a game.</value> /// <value><c>true</c> if in menu; <c>false</c> when loading or in a game.</value>
public bool InMenu public bool InMenu
{ {
get => appEngine.IsInMenu; get => Game.menuEngine.IsInMenu;
} }
/// <summary> /// <summary>
@ -96,14 +93,26 @@ namespace TechbloxModdingAPI.App
EnqueueError(errorHandlerInstance, popup); 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(); 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() internal static void Init()
{ {
@ -116,11 +125,6 @@ namespace TechbloxModdingAPI.App
EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError") EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError")
.MakeGenericMethod(errorHandler, errorHandle) .MakeGenericMethod(errorHandler, errorHandle)
.Invoke(null, new object[0]); .Invoke(null, new object[0]);
/*HandleErrorClosed = (Action<object>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenHandlePopupClosed")
.MakeGenericMethod(errorHandler)
.Invoke(null, new object[0]);*/
// register engines
MenuEngineManager.AddMenuEngine(appEngine);
} }
// Creating delegates once is faster than reflection every time // Creating delegates once is faster than reflection every time
@ -145,14 +149,23 @@ namespace TechbloxModdingAPI.App
return enqueueCasted; 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"); if (_errorPopup.Close != null)
MethodInfo handlePopupClosed = AccessTools.Method(errorHandler, "HandlePopupClosed"); return _errorPopup;
Action<T> handleSimple = Type errorHandler = handler.GetType();
(Action<T>) Delegate.CreateDelegate(typeof(Action<T>), handlePopupClosed); FieldInfo field = AccessTools.Field(errorHandler, "errorPopup");
Action<object> handleCasted = (object instance) => handleSimple((T) instance); var errorPopup = (ErrorPopup)field.GetValue(handler);
return handleCasted; 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)] [APITestCase(TestType.Menu)]
public static void TestPopUp2() public static void TestPopUp2()
{ {
Client c = new Client(); Client.Instance.PromptUser(popup2);
c.PromptUser(popup2);
//c.CloseCurrentPrompt();
} }
[APITestCase(TestType.Menu)] [APITestCase(TestType.Menu)]
public static void TestPopUp1() public static void TestPopUp1()
{ {
Client c = new Client(); Client.Instance.PromptUser(popup1);
c.PromptUser(popup1); }
//c.CloseCurrentPrompt();
[APITestCase(TestType.Menu)]
public static void TestPopUpClose1()
{
Client.Instance.CloseCurrentPrompt();
}
[APITestCase(TestType.Menu)]
public static void TestPopUpClose2()
{
Client.Instance.CloseCurrentPrompt();
} }
} }
#endif #endif

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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

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

View file

@ -15,6 +15,32 @@ namespace TechbloxModdingAPI.Blocks
SteelBodyworkRustedPaint, SteelBodyworkRustedPaint,
SteelBodyworkHeavyRust, SteelBodyworkHeavyRust,
WoodVarnishedDark, WoodVarnishedDark,
Chrome Chrome,
FenceChainLink,
ConcreteUnpainted,
Grid9x9,
CeramicTileFloor,
PlasticBumpy,
PlasticDustySmeared,
AluminiumGarageDoor,
SteelRigidScratched,
AluminiumBrushedTinted,
AluminiumSheetStained,
ConcretePaintedGrooves,
PlasticSpecklySatin,
SteelBodyworkPaintedChipped,
WoodPainted,
WoodRoughGrungy,
Boundary,
Emissive,
AircraftPanelingRivetedPainted,
AircraftPanelingRivetedMetallic,
SteelBodyworkPearlescent,
SteelBodyworkRadWrap,
SteelBodyworkGlitter,
BouncyRubber,
BouncyRubberTieDye,
BrickPainted,
FuturisticPanelingRivetedPainted,
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.UECS; using RobocraftX.DOTS;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using Unity.Mathematics; using Unity.Mathematics;
@ -7,6 +7,7 @@ using UnityEngine;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {
@ -40,7 +41,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block); ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block); ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(block); ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(block);
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntityOrDefault<UECSPhysicsEntityStruct>(block); var phyStruct = this.entitiesDB.QueryEntityOptional<DOTSPhysicsEntityStruct>(block);
// main (persistent) rotation // main (persistent) rotation
Quaternion newRotation = rotStruct.rotation; Quaternion newRotation = rotStruct.rotation;
newRotation.eulerAngles = vector; newRotation.eulerAngles = vector;
@ -50,16 +51,18 @@ namespace TechbloxModdingAPI.Blocks.Engines
// rendered rotation // rendered rotation
transStruct.rotation = newRotation; transStruct.rotation = newRotation;
// collision rotation // collision rotation
if (phyStruct.ID != EGID.Empty) if (phyStruct)
{ //It exists { //It exists
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.Get().dotsEntity,
new Unity.Transforms.Rotation new Unity.Transforms.Rotation
{ {
Value = rotStruct.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; return ((Quaternion)rotStruct.rotation).eulerAngles;
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,25 +5,68 @@ using RobocraftX.CR.MainGame;
using RobocraftX.FrontEnd; using RobocraftX.FrontEnd;
using RobocraftX.StateSync; using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Schedulers;
using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Engines namespace TechbloxModdingAPI.Engines
{ {
[HarmonyPatch] [HarmonyPatch]
class GameLoadedEnginePatch static class GameLoadedTimeStoppedEnginePatch
{ {
public static void Postfix(StateSyncRegistrationHelper stateSyncReg) public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{ {
// register all game engines, including deterministic // register all game engines, including deterministic
GameEngineManager.RegisterEngines(stateSyncReg); GameEngineManager.RegisterEngines(stateSyncReg);
// register command engines
/*CommandLineCompositionRoot.Compose(contextHolder, stateSyncReg.enginesRoot, reloadGame, multiplayerParameters,
stateSyncReg); - uREPL C# compilation not supported anymore */
CommandManager.RegisterEngines(stateSyncReg.enginesRoot);
} }
public static MethodBase TargetMethod() public static MethodBase TargetMethod()
{ {
return AccessTools.Method(typeof(MainGameCompositionRoot), "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] [HarmonyPatch]
class MenuLoadedEnginePatch class MenuLoadedEnginePatch
{ {

View file

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

View file

@ -14,7 +14,7 @@ namespace TechbloxModdingAPI.Engines
/// <summary> /// <summary>
/// Engine interface to handle ModEventEntityStruct events emitted by IEventEmitterEngines. /// Engine interface to handle ModEventEntityStruct events emitted by IEventEmitterEngines.
/// </summary> /// </summary>
public interface IReactionaryEngine<T> : IApiEngine, IReactOnAddAndRemove<T>, 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 TechbloxModdingAPI.App;
using RobocraftX.Common.Input;
using Svelto.ECS;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Input namespace TechbloxModdingAPI.Input
{ {
public static class FakeInput public static class FakeInput
{ {
private static readonly FakeInputEngine inputEngine = new FakeInputEngine(); internal static readonly FakeInputEngine inputEngine = new FakeInputEngine();
/// <summary> /// <summary>
/// Customize the local input. /// 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) public static void ActionInput(uint playerID = uint.MaxValue, bool toggleMode = false, bool forward = false, bool backward = false, bool up = false, bool down = false, bool left = false, bool right = false, bool sprint = false, bool toggleFly = false, bool alt = false, bool primary = false, bool secondary = false, bool tertiary = false, bool primaryHeld = false, bool secondaryHeld = false, bool toggleUnitGrid = false, bool ctrl = false, bool toggleColourMode = false, bool scaleBlockUp = false, bool scaleBlockDown = false, bool rotateBlockClockwise = false, bool rotateBlockCounterclockwise = false, bool cutSelection = false, bool copySelection = false, bool deleteSelection = false)
{ { // TODO: We can only alter our own inputs clientside
if (playerID == uint.MaxValue) ref var currentInput = ref inputEngine._localInputCache;
{
playerID = inputEngine.GetLocalPlayerID();
}
ref LocalPlayerInputEntityStruct currentInput = ref inputEngine.GetPlayerInputRef(playerID);
//Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}"); //Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}");
// set inputs // set inputs
if (toggleMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningMode; if (toggleMode) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningModePlay; //TODO: Test, play
if (forward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Forward; if (forward) currentInput |= RobocraftX.Common.Input.ActionInput.Forward;
if (backward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Backward; if (backward) currentInput |= RobocraftX.Common.Input.ActionInput.Backward;
if (up) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Up; if (up) currentInput |= RobocraftX.Common.Input.ActionInput.Up;
if (down) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Down; if (down) currentInput |= RobocraftX.Common.Input.ActionInput.Down;
if (left) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Left; if (left) currentInput |= RobocraftX.Common.Input.ActionInput.Left;
if (right) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Right; if (right) currentInput |= RobocraftX.Common.Input.ActionInput.Right;
if (sprint) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Sprint; if (sprint) currentInput |= RobocraftX.Common.Input.ActionInput.Sprint;
//if (toggleFly) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SwitchFlyMode; //if (toggleFly) currentInput |= RobocraftX.Common.Input.ActionInput.SwitchFlyMode;
//if (alt) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.AltAction; //if (alt) currentInput |= RobocraftX.Common.Input.ActionInput.AltAction;
if (primary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.PrimaryAction; if (primary) currentInput |= RobocraftX.Common.Input.ActionInput.PrimaryActionClick;
if (secondary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SecondaryAction; if (secondary) currentInput |= RobocraftX.Common.Input.ActionInput.SecondaryActionClick;
if (tertiary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.TertiaryAction; if (tertiary) currentInput |= RobocraftX.Common.Input.ActionInput.TertiaryAction;
if (primaryHeld) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.PrimaryActionHeld; if (primaryHeld) currentInput |= RobocraftX.Common.Input.ActionInput.PrimaryActionHeld;
if (secondaryHeld) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SecondaryActionHeld; if (secondaryHeld) currentInput |= RobocraftX.Common.Input.ActionInput.SecondaryActionHeld;
//if (toggleUnitGrid) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleUnitGrid; //if (toggleUnitGrid) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleUnitGrid;
if (ctrl) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CtrlAction; if (ctrl) currentInput |= RobocraftX.Common.Input.ActionInput.CtrlAction;
if (toggleColourMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleColourMode; if (toggleColourMode) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleColourMode;
if (scaleBlockUp) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ScaleBlockUp; if (scaleBlockUp) currentInput |= RobocraftX.Common.Input.ActionInput.ScaleBlockUp;
if (scaleBlockDown) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ScaleBlockDown; if (scaleBlockDown) currentInput |= RobocraftX.Common.Input.ActionInput.ScaleBlockDown;
if (rotateBlockClockwise) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.RotateBlockClockwise; if (rotateBlockClockwise) currentInput |= RobocraftX.Common.Input.ActionInput.RotateBlockClockwise;
if (rotateBlockCounterclockwise) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.RotateBlockAnticlockwise; if (rotateBlockCounterclockwise) currentInput |= RobocraftX.Common.Input.ActionInput.RotateBlockAnticlockwise;
if (cutSelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CutSelection; if (cutSelection) currentInput |= RobocraftX.Common.Input.ActionInput.CutSelection;
if (copySelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CopySelection; if (copySelection) currentInput |= RobocraftX.Common.Input.ActionInput.CopySelection;
if (deleteSelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.DeleteSelection; 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() public static void Init()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -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

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

View file

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

View file

@ -1,20 +1,14 @@
using System; using DataLoader;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DataLoader;
using HarmonyLib; using HarmonyLib;
using RobocraftX; using RobocraftX;
using RobocraftX.Common.Utilities; using RobocraftX.CR.MainGame;
using RobocraftX.GUI; using RobocraftX.GUI;
using RobocraftX.Multiplayer; using RobocraftX.Multiplayer;
using RobocraftX.Rendering;
using Svelto.Context; using Svelto.Context;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Schedulers; using Svelto.ECS.GUI;
using Techblox.GameSelection;
using UnityEngine; using UnityEngine;
using Unity.Entities; using Unity.Entities;
using Unity.Physics.Systems; using Unity.Physics.Systems;
@ -72,14 +66,6 @@ namespace TechbloxModdingAPI.Utility
} }
} }
public static SimpleEntitiesSubmissionScheduler _mainGameSubmissionScheduler
{
get
{
return (SimpleEntitiesSubmissionScheduler)fgcr?.Field("_sub").Field("_mainGameSubmissionScheduler").GetValue();
}
}
public static BuildPhysicsWorld _physicsWorldSystem public static BuildPhysicsWorld _physicsWorldSystem
{ {
get get
@ -112,14 +98,6 @@ namespace TechbloxModdingAPI.Utility
} }
} }
public static PhysicsUtility _physicsUtility
{
get
{
return (PhysicsUtility)fgcr?.Field("_physicsUtility").GetValue();
}
}
/*public static UnityEntitySubmissionScheduler _frontEndSubmissionScheduler /*public static UnityEntitySubmissionScheduler _frontEndSubmissionScheduler
{ {
get get
@ -144,11 +122,11 @@ namespace TechbloxModdingAPI.Utility
} }
} }
public static ECSGameObjectResourceManager _eCsGameObjectResourceManager public static ECSMainGameResourceManagers _managers
{ {
get get
{ {
return (ECSGameObjectResourceManager)fgcr?.Field("_eCsGameObjectResourceManager").GetValue(); return (ECSMainGameResourceManagers)fgcr?.Field("_gameManagers").GetValue();
} }
} }
@ -168,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; private static Traverse fgcr;
public static void Init(FullGameCompositionRoot instance) 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.Linq;
using System.Text;
using System.Threading.Tasks;
using RobocraftX.StateSync; using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
@ -26,10 +23,10 @@ namespace TechbloxModdingAPI.Utility
{ {
Logging.MetaDebugLog($"Registering Game IApiEngine {engine.Name}"); Logging.MetaDebugLog($"Registering Game IApiEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine); _lastEngineRoot.AddEngine(engine);
if (typeof(IFactoryEngine).IsAssignableFrom(engine.GetType())) if (engine is IFactoryEngine factoryEngine)
{ factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory();
((IFactoryEngine)engine).Factory = _lastEngineRoot.GenerateEntityFactory(); if (engine is IFunEngine funEngine)
} funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions();
} }
} }
@ -66,6 +63,7 @@ namespace TechbloxModdingAPI.Utility
var enginesRoot = helper.enginesRoot; var enginesRoot = helper.enginesRoot;
_lastEngineRoot = enginesRoot; _lastEngineRoot = enginesRoot;
IEntityFactory factory = enginesRoot.GenerateEntityFactory(); IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions();
foreach (var key in _gameEngines.Keys) foreach (var key in _gameEngines.Keys)
{ {
Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}"); Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}");
@ -75,6 +73,8 @@ namespace TechbloxModdingAPI.Utility
enginesRoot.AddEngine(_gameEngines[key]); enginesRoot.AddEngine(_gameEngines[key]);
if (_gameEngines[key] is IFactoryEngine factEngine) if (_gameEngines[key] is IFactoryEngine factEngine)
factEngine.Factory = factory; factEngine.Factory = factory;
if (_gameEngines[key] is IFunEngine funEngine)
funEngine.Functions = functions;
} }
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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

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

View file

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

View file

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