Compare commits

..

284 commits

Author SHA1 Message Date
5dff88d703
Switch from IPA to BepInEx
- Removed a bunch of test code
- Preparing for 3.0
2023-08-22 00:02:26 +02:00
a8a451f8e4
Merge TB update feature branch 2023-03-30 01:22:10 +02:00
67f32b8810
Improved and fixed publish queue detection and block test
- Made the PublishEntityChangesDelayed() method use the internals of Svelto.ECS to determine if it should wait
-- I had this code for a while but it used too much reflection to my liking
-- Now I made the reflection code nicer and a bit safer
- Fixed float comparisons: last time I didn't actually used abs() for float3 but I found this even better method
2023-03-30 01:17:31 +02:00
b3b1e9b9e7
Update reference paths to allow for RC2 dev as well 2022-10-18 20:19:41 +02:00
e0cd7f6aec
Fix assembly editing and add more of it
- It breaks the game atm, not sure why exactly but it's probably not a good thing
2022-10-05 01:53:54 +02:00
23439abde3
Add new blocks and materials, make every type public in the game, fix entity publish
- Probably should've committed more
- Added new block IDs and a material (also fixed material names)
- Added missing player states
- Added a class to make every type public in the game's code, so we don't need to worry about internal components
- We don't need to worry about anticheat so it should be fine - will need to be made into its own exe though
- Fixed delayed entity publishing and set the limits based on the consumers (just 30 almost everywhere)
2022-10-04 01:47:09 +02:00
5e90c5ee26
Fix all compiler issues and add Count property and smart ToArray() function to RefCollection
- Collections can be converted into arrays using a mapper and a predicate function
2022-10-02 01:34:51 +02:00
5117b69500
Fix RefCollection and start using it to query multiple users
- I overcomplicated in the beginning
- It doesn't shorten the code that much but it provides a stable interface and it's easier to use so I guess it's nice
2022-09-29 01:26:51 +02:00
f70b65e796
Start updating to Techblox 2022.08.11.09.42 and start work on RefCollection
What have I got myself into
2022-09-29 00:29:12 +02:00
55344d1352
Start updating to Techblox 2022.05.25.11.05
Resolved compiler errors
Mostly by removing erroring code
2022-06-01 16:54:17 +02:00
dfe1bfb504
Begin updating to Techblox 2022.04.28.14.02
Updated project generator script to always order assemblies (it didn't do that for me on Linux) and to fix minor issues
2022-04-29 02:07:46 +02:00
a610623644 Bump version 2022-04-12 03:18:28 +02:00
f9aa6ce2bb Re-add object ID class, add some wheel rig properties, remove old game assembly refernces 2022-04-12 00:52:24 +02:00
23abe47c72 Update to Techblox 2022.04.01.10.32
- Updated project to use .NET Standard 2.1, which is what the game uses
- Updated CodeGenerator to use .NET 6
2022-04-08 03:25:05 +02:00
c0ef8f1fae Fix support for accessing properties using reflection
The test still crashes the game
2022-03-27 03:49:45 +02:00
c4a9125ed3 Update to Techblox 2022.03.17.17.24 2022-03-20 18:08:16 +01:00
3eecdf2cf5 Add key collection to weak dictionary and compact code 2022-02-24 01:02:35 +01:00
2db7b607f0 Improve UI elements (IMGUI) 2022-02-23 02:25:34 +01:00
7f63944a6e Block fixes, add mass and complexity properties, make Player.LocalPlayer return null if not found 2022-02-19 02:25:58 +01:00
c6dae688fe Update to Techblox 2022.02.17.10.32 2022-02-18 23:09:56 +01:00
7b2ac973d8 Bump version to v2.2.0 2022-02-13 20:21:42 +01:00
0ec47cd38b Add method to get ghost block 2022-02-13 18:27:54 +01:00
ddaa933e7d Add option to delay entity change publish and remove reflection stuff
Neither of them work actually
Added some delay between tests
2022-02-07 00:25:01 +01:00
5fea7dc3b3 Add support for generating block classes that use reflection to access internal components
Added Engine properties again
2022-02-06 03:11:51 +01:00
4684b33c69 Fix tests, getting machine blocks, block labels and visuals
- Checking the material property again, it seems to work now
- Fixed the Seat events not triggering during tests (the player in build and in sim is different)
- Fixed Game.GetAllBlocksInGame() returning environment blocks (since a Game refers to a machine save)
- Fixed the Block.Label property
- Fixed the block visuals not being updated after applying changes
2022-01-31 23:20:03 +01:00
d27bcee8d5 Update to Techblox 2022.01.25.15.52
- Fixed compilation errors
- Fixed patching errors and added missing anti-cheat patch
- Added check to verify that the init data has been removed from blocks once they are placed in game
- Removed block place event deduplication as it seems to be not needed anymore
- Fixed async tests not properly running
- Added Player.State
- Attempted to fix seat entering/leaving (we can only send inputs in client code)
- Fixed the weak dictionary ContainsKey returning true even if the item is no longer there
2022-01-30 04:32:10 +01:00
09d3c5e81c Merge branch 'preview'
# Conflicts:
#	Automation/gen_csproj.py
#	TechbloxModdingAPI/TechbloxModdingAPI.csproj
2022-01-29 20:53:07 +01:00
966fdd4c3a Fix even more issues uncovered by tests
- Fixed the time mode toggle not working during testing (changed the runner)
- Made the testing thing wait until the time toggle finishes
- Fixed the Game.Enter/Exit event being triggered on time mode change
- Added a way to spawn and despawn the player's machine (which doesn't work yet)
- Fixed the Player.Id property always being 0
- Attempted to fix the fake action inputs not working in simulation
2022-01-07 02:14:58 +01:00
5602ef9268 All kinds of fixes of issues during automatic tests
- Fixed toggling time running mode
- Fixed closing popups
- Added support for pressing the buttons on a popup
- Added error handling to Main.Init()
- Automatically closing the beta message in the test plugin
- Fixed Game.EnterGame() causing a crash in the game
2021-12-28 15:09:01 +01:00
93a0b2287a Added player join/leave events and fix errors
- Fixed anticheat status error spam
- Fixed IMGUI not actually running on OnGUI because that runner was changed in Svelto
- Added player join and leave events
- Made Game.Enter only trigger when both the game has finished loading *and* the local player has joined
2021-12-27 02:28:09 +01:00
4ac8d53a2d Organize anti-anticheat, add block IDs, fix crash when adding event handlers multiple times 2021-12-26 23:37:02 +01:00
f817becc6e Resolve all compile-time and patching errors, remove anticheat in singleplayer 2021-12-16 21:13:45 +01:00
2a1782cd82 Start updating to 2021.12.14.17.00
A bunch of errors still
2021-12-15 03:46:38 +01:00
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
74d5a5c6b1
Fix default values getting changed and add test 2021-06-23 01:58:01 +02:00
76faa69c74 Add support for enabling the screenshot taker, even in sim 2021-06-11 19:51:32 +02:00
52ccbe4dad Fix tests and add new materials 2021-06-10 23:57:06 +02:00
0b2ffef0d3 Update block IDs 2021-06-09 22:03:15 +02:00
99f077a917 Update to Techblox 2021.06.08.16.19
Added check for time mode toggle to avoid crashing the game
Added support for having the ref folder outside the solution in gen_csproj.py
Removed BlockIdentifiers class
Added check for invalid player ID when placing blocks
Resolved compilation errors
2021-06-09 20:11:31 +02:00
c1c226ef2a Added support for setting default color/material and static blocks
Also fixed setting game name/description

When setting the block color or material to default it will look up the default value to use
Blocks can now be set to be static so they don't fall or move at all
2021-06-04 23:07:06 +02:00
NGnius (Graham)
06cb911ea3 Update IMGUI to something roughly TB-like 2021-05-31 17:59:25 -04:00
b31eaa20c0
Check if block type is correct 2021-05-30 02:12:38 +02:00
94c0c1370b
Removed 2 non-OOP classes and fixed fly cam teleport
Remvoed Hotbar and GameClient since their functions are also implemented in OOP classes
Added static methods to add/remove persistent debug info
Made some patches and other things internal
Added support for FlyCams in SetLocation
2021-05-30 01:34:30 +02:00
b8fd14d934 Move speed settings to Player and make it work with players
Probably
2021-05-28 02:52:42 +02:00
5bfd0b7f10 Integrate FlyCam class into Player
Using QueryEntityOptional directly with the player properties
Character structs are camera structs in build mode
The FlyCam rotation is not updated in build mode, only the camera is
2021-05-28 02:12:54 +02:00
220eb02a19
Return descriptions with command names, selected block/color fix 2021-05-25 01:20:46 +02:00
e8515ef42b
Fix events not firing and event exception handling
Copying to Plugins folder on build
Registering deterministic game engines automatically
Each event handler is wrapped so if one fails it will still trigger the rest
2021-05-23 20:53:55 +02:00
f5e3010e48
Removed all obsolete classes and some commented out code 2021-05-21 00:09:36 +02:00
1cbe252727
Move block engines into their own namespace 2021-05-20 23:37:10 +02:00
b3f7dcd36d
Add start of Engine class, removed nonexistent blocks
Not all engine properties are added (yet)
The old block types can be brought back when/if they come back, potentially with different properties
2021-05-20 23:26:22 +02:00
e9df67f462
Use Block.New everywhere, testing *every block property*
Fixed prefab update for nonexistent blocks
Removed Type from block placed/removed event args
Added test to check the block ID enum (whether it has any extra or missing IDs)
Added test to place every block on the ID enum
Added test to set and verify each property of each block type (type-specific properties are also set when they can be through the API)
Added support for enumerator test methods with exception handling
2021-05-19 01:40:15 +02:00
70b322583a
Fix setting the material of a block
Also fixed ID of wood material
2021-05-18 20:00:24 +02:00
4f0645492c
Fix block color and group 2021-05-18 00:44:09 +02:00
58d703f502
Fix block tests and add test command to toggle time mode 2021-05-17 14:55:13 +02:00
NGnius
db08bf1ac0 Fix docs (hopefully) 2021-05-12 20:00:33 -04:00
dd2680abd5
Set the grid scale as well when changing the scale 2021-05-13 01:41:52 +02:00
4807c12387
Fix placing blocks
Most of the removed initialitzers are actually important
Also, the prefab ID was not calculated correctly
Also, the category is 1, not sure why but it is
2021-05-13 00:13:31 +02:00
3432a1ae33
Return block objects based on the group, not a type param
Replaced typeToGroup with GroupToConstructor
The block object's type is determined by the exclusive group instead of a type parameter
Removed the Specialise() method, the API should always return specialised objects

This fixes the not supported exception but not the game crash that follows
2021-05-12 02:33:01 +02:00
7a53e1d32f
Fix command registration 2021-05-12 01:34:40 +02:00
aa12b848d0
Merge branch 'feature-ecs_object_base' 2021-05-12 00:51:56 +02:00
b6b9a29a3c
Convert more things to use EcsObjectBase
Though the major benefit is only for blocks right now (using initializers)
2021-05-12 00:49:01 +02:00
6fedf90380
Remove struct layout stuff
It broke everything using the type
2021-05-12 00:25:07 +02:00
3eef859095
Update gen_csproj script and references 2021-05-11 22:56:36 +02:00
858a5c9b5c
Fix remaining errors, add support for managed entity DB 2021-05-11 00:56:46 +02:00
d238c97906
Remove block info getters and setters
Regex is great

GetBlockInfo\(this, \((\w+) (\w+)\) ?=> ?\2(.+)\);
GetBlockInfo<$1>(this)$3;

SetBlockInfo\(this, \(ref (\w+) (\w+), \w+ (\w+)\) ?=> \2(.*) = \3,\s*value\);
GetBlockInfo<$1>(this)$4 = value;
2021-05-10 23:08:15 +02:00
61184145a9
Start using new extension methods, code cleanup
Removed all of the different block property getter methods
2021-05-10 22:45:07 +02:00
2d99d1d478
Generalize optional references and init data
Added extension methods to query data from ECS objects
Added base class for ECS objects
Added support for representing in-construction ECS objects with an OptionalRef<T>
2021-05-10 02:04:59 +02:00
78ee3b3bcd
Fix block type check on placement 2021-05-10 01:38:15 +02:00
aea3ef3623
Remove AsyncUtils, fix FlyCam and GetThingLookedAt() 2021-05-03 01:25:26 +02:00
62afd3b780
Some file renames that were missing 2021-05-03 00:17:49 +02:00
5172b13b7c
Update readme and version 2021-05-02 02:08:22 +02:00
c914b5b393
Renamed all references of Gamecraft to Techblox
Except those that actually refer to the game's code
2021-05-02 01:56:20 +02:00
a6f52070ee
Rename to TechbloxModdingAPI 2021-05-02 01:08:25 +02:00
807470e289
Add new block types and improve listing them
Now it prints them ordered and mostly suitable to be used in code (it only needs a couple replaces)
2021-05-01 00:38:27 +02:00
df6a2e84e1
Update to Techblox 2021.04.29.18.37 2021-04-30 22:36:54 +02:00
6e03847ab0
FlyCam additions, improve struct
Added property to get the camera from the player
Removed pointer magic
2021-04-27 01:52:54 +02:00
55b38f1678
Start working on FlyCam and create an overcomplicated struct
Just some native code that's totally unnecessary
2021-04-26 03:12:22 +02:00
eb7a09ed22
Fixes, move command patch out of the test class
Removed some command line engines that shouldn't be registered
Fixed registering custom commands - registering it with the existing ones
2021-04-25 02:07:31 +02:00
6a2459b3e7
Attempts to bring console commands back (test) 2021-04-24 03:41:37 +02:00
cc4850a073
Fix fake input 2021-04-20 01:23:39 +02:00
677c8b0907
Add constructor for placing block, remove most PlaceNew args 2021-04-19 19:32:14 +02:00
1f688195af
Add support for flipped blocks and auto-wiring, other fixes 2021-04-19 03:13:00 +02:00
9a4ff858f3
Improve color API and add material API 2021-04-16 01:40:30 +02:00
124ef410c7
Attempt to bring console back and update block ID list 2021-04-13 02:05:16 +02:00
98e00de642
Fix all startup errors 2021-04-12 17:37:51 +02:00
2d41026a05
Turned the rest of the errors into TODOs 2021-04-11 02:36:00 +02:00
a6b69d94c9
Start compatibility with Techblox
Added some TODOs as well
2021-04-10 02:02:47 +02:00
NGnius (Graham)
37e3c6f718 Remove debug FMOD patches 2020-12-28 13:47:08 -05:00
NGnius (Graham)
0ef875b6b2 Document undocumented IMGUI element classes 2020-12-27 18:57:23 -05:00
d954060a5a Add ability to change properties of existing blocks
And not storing custom block data for now
2020-12-27 21:13:49 +01:00
fdc47832f4 Store custom block IDs in save files 2020-12-26 01:59:06 +01:00
NGnius (Graham)
95574a50f8 Merge branch 'master' of https://git.exmods.org/modtainers/GamecraftModdingAPI 2020-12-21 16:57:32 -05:00
NGnius (Graham)
1c014e36ac Add IMGUI styling and initial OOP implementation 2020-12-21 16:31:57 -05:00
879901f4b9 Add new block IDs, a property, 2 tests and fixes 2020-12-20 00:05:02 +01:00
6a90739197 Attempt to use custom cube category 2020-12-19 21:43:49 +01:00
9c5c980c0b Merge pull request 'Add custom block support to the API and update to latest GC version' (#6) from customblocks into master 2020-12-17 21:21:18 +00:00
712ece86db Add custom block registration functionality and a test 2020-12-17 20:20:46 +01:00
a7f6a16231 Update to Gamecraft 2020.12.16.14.19 and custom block stuff
- Fixed the crash on second time start
- Tweaked more stuff about the block

Breaking changes coming from FMOD 2.0:
- Audio[int index] changed to Audio[PARAMETER_ID index]
- Audio.Parameters removed
2020-12-17 02:34:36 +01:00
4e16f251ee Don't use the intended method to create a CubeListData
It adds it with its ID as key but the ID hasn't been set at that point
It works until the second simulation start now
2020-12-13 20:21:46 +01:00
78f0ea0162 Use the intended method to create a CubeListData
The block can be selected but not placed
2020-12-12 23:08:56 +01:00
5dfb01ef0b Use the console block's material again - IT WORKS
It shows up in the inventory but crashes when selected
2020-12-12 16:59:52 +01:00
432d6bcf96 Use the same (physics) componentts and attempt to use custom material 2020-12-12 02:28:42 +01:00
NGnius (Graham)
be7d8ba33a Merge branch 'master' of https://git.exmods.org/modtainers/GamecraftModdingAPI 2020-12-11 12:16:15 -05:00
56a64daa18 Merge branch 'master' into customblocks
# Conflicts:
#	GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
2020-12-11 17:16:05 +01:00
ab1ae51ece Update to Gamecraft 2020.11.30.16.02 2020-12-11 17:11:24 +01:00
NGnius (Graham)
404c47c7c0 Bump version to 1.8.0 2020-11-18 16:46:23 -05:00
fad3b5cbf4 Fix picking block groups... 2020-11-14 22:43:45 +01:00
1f911b1d32 Fix move/rotate during init, add blueprint properties 2020-11-14 18:29:48 +01:00
f30dcd251f Displaying blueprint before placing, enums, ToString()s
Added support for getting the player's current building mode (build, color, config, blueprint)
Added support for getting the current game's mode (building, playing, prefab etc.)
2020-11-14 02:52:16 +01:00
680721256c Add support for copying wires, some fixes and additions
Removing blocks from groups when they are removed from the game
Attempted to update graphics when changing blocks
Disallowing changing the block group after creation, now that we can copy blocks
2020-11-13 23:59:37 +01:00
64b42830a3 Blueprint fixes, bump version, add block copy support
Fixed getting the selected blueprint
Fixed block groups not being assigned to first block
2020-11-13 21:40:32 +01:00
d744aaab79 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-13 17:02:28 +01:00
3dd61b5e4f Replace ToManagedArray() and fix getting blocks from group 2020-11-13 17:02:27 +01:00
1c4e2a0db2 Add support for setting and placing blueprints 2020-11-13 17:02:27 +01:00
4f8feaa24b Add new blocks and some blueprint/block group support 2020-11-13 17:02:27 +01:00
NGnius (Graham)
8eec1358e9 Fix harmony patch error due to fixed name 2020-11-13 17:02:27 +01:00
NGnius (Graham)
08138e3589 Fix build errors from beta hotfix 1 2020-11-13 17:02:27 +01:00
987fbe673a Fix initial issues and add error on patch fail
Fixed compilation and loading issues for 2020.10.27.17.13
2020-11-13 17:01:46 +01:00
4580ae3b66 Add ability to create & move block groups & other stuff
Added a way to store block groups as blueprints
Blocks can be added/removed from block groups, although it doesn't work well atm
Added some patches to the test class in an attempt to debug an unrelated issue
Added a command to test placing a block group
Added a SelectedBlueprint property to the Player class
2020-11-12 02:39:58 +01:00
f1376f5df6 Replace ToManagedArray() and fix getting blocks from group 2020-11-10 23:08:27 +01:00
2179ba6386 Add support for setting and placing blueprints 2020-11-10 19:28:36 +01:00
1a986056a1 Add new blocks and some blueprint/block group support 2020-11-10 16:37:20 +01:00
NGnius (Graham)
d891f12701 Fix harmony patch error due to fixed name 2020-11-09 16:33:12 -05:00
NGnius (Graham)
1cb663b4d1 Fix build errors from beta hotfix 1 2020-11-09 16:18:25 -05:00
0bd348bd47
Fix initial issues and add error on patch fail
Fixed compilation and loading issues for 2020.10.27.17.13
2020-10-29 00:37:47 +01:00
3929144171
Merge remote-tracking branch 'origin/master' into preview
# Conflicts:
#	GamecraftModdingAPI/Block.cs
#	GamecraftModdingAPI/GamecraftModdingAPI.csproj
2020-10-28 21:10:30 +01:00
NGnius (Graham)
c6a1ea35cc Add damped spring 2020-10-27 11:59:21 -04:00
b0b496f22f Fix ConcurrentModificationException and some attempts 2020-10-22 02:34:59 +02:00
4701b3577d Make BlockColor.Index a property and other fixes 2020-10-02 17:06:06 +02:00
abbb83da26 Chunk and cluster fixes and improvements & bump version
Added Cluster.GetSimBodies() and SimBody.GetBlocks()
Fixed some issues with IDs and bad handling of them
2020-10-02 16:40:06 +02:00
64aace3bde Remove ScalingEngine.Setup() and add object ID to dict 2020-10-02 14:52:37 +02:00
9e9f56881f Add hotfix blocks and Player.LocalPlayer 2020-10-02 14:52:37 +02:00
c9e71d84b4 Add support for getting the RGB of block colors
Only works if the constructors are used
2020-10-02 14:52:37 +02:00
NGnius (Graham)
4dfa7b0f4e Implement SFX block API and bump version 2020-10-02 14:52:33 +02:00
1a0c98dd67 Add the rest of the blocks 2020-10-02 14:52:26 +02:00
f2ce037564 Fix TextBlock.Text=null, most new blocks and others 2020-10-02 14:52:26 +02: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
c06ed340a2 Using the console block's material
Progressed a lot
2020-09-17 23:08:26 +02:00
f295f712b6 Merge branch 'master' into customblocks
# Conflicts:
#	GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
2020-09-17 19:18:00 +02:00
NGnius (Graham)
aae2057972 Convert relevant blocks to wireable blocks and fix wire connect during block init 2020-08-23 09:59:13 -04:00
NGnius (Graham)
daf4a24bc9 Fix namespace build error from unused using statement (I deleted it) 2020-08-22 09:26:51 -04:00
NGnius (Graham)
11b94e384e Update refs 2020-08-22 09:25:14 -04:00
cfdc5e8c26 Fixes, block IDs, cluster & chunk health support 2020-08-22 09:24:18 -04:00
NGnius (Graham)
fd97194903 Fix build issues for latest Gamecraft preview version 2020-08-22 09:23:59 -04:00
2172364d26 Fixes, block IDs, cluster & chunk health support 2020-08-13 16:59:13 +02:00
NGnius (Graham)
89f354b647 Fix Game Over detection 2020-08-13 10:12:36 -04: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
NGnius (Graham)
8354123169 Cache additional info to avoid entity queries 2020-08-07 12:05:49 -04:00
NGnius (Graham)
7f5a36cb62 Automate version bumping because I keep forgetting doxygen.conf 2020-08-04 15:10:07 -04:00
NGnius (Graham)
e56871f5ef Fix wire entity queries when its not submitted yet 2020-08-03 13:24:35 -04:00
NGnius (Graham)
708dbdd81d Add wiring API and improve signal support 2020-08-03 12:45:38 -04:00
NGnius (Graham)
ca0e6e089d Mark old event system as deprecated 2020-08-03 12:43:00 -04:00
NGnius (Graham)
b81562ea58 Fix sfx block test default value 2020-07-25 12:12:48 -04:00
057a030c20 Update music block and attempt to fix test 2020-07-24 11:11:53 -04:00
15485481a2 Add some info and prev. value for setters 2020-07-24 11:11:53 -04:00
b53dff5d12 Remove initializer data once the block is placed 2020-07-24 11:11:53 -04:00
5264d98ce7 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-24 11:11:53 -04:00
5e335e78ff Implement init for position and rotation 2020-07-24 11:11:52 -04:00
7336fe8353 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-24 11:11:52 -04:00
89d32956d9 Automatically invoke the correct block constructor
And store delegates of dynamic methods invoking constructors
Tested with the automated tests
2020-07-24 11:11:52 -04:00
NGnius (Graham)
ea8a9184bc Add popup UI method to Client 2020-07-24 11:11:52 -04:00
NGnius (Graham)
926d968eed Add sfx block support 2020-07-24 11:11:48 -04:00
NGnius (Graham)
421faf7167 Improve dev info in README 2020-07-24 11:10:20 -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
aa0aefd41b Find block when group is unknown 2020-07-11 02:26:36 +02:00
f403feb298 Update to Gamecraft 2020.06.17.08.41 (preview)
Removed BlockIdentifiers.OWNED_BLOCKS as the original got replaced with an array
Added the correct group for each supported functional block
Removed EntityFactory property from IEntitySerializer as it is provided on deserialization
2020-07-11 00:30:58 +02:00
NGnius (Graham)
6f589f1744 Create Player tests 2020-07-01 13:43:56 -04:00
NGnius (Graham)
b376133d28 Create some test cases for blocks 2020-06-30 20:43:45 -04:00
NGnius (Graham)
60f231f939 Add standard asserts 2020-06-30 20:43:14 -04:00
NGnius (Graham)
b6a5074fd2 Add some state info and save method 2020-06-26 19:37:58 -04:00
NGnius (Graham)
189c3ca2a5 Document App and Test additions (+ minor tweaks) 2020-06-23 13:49:42 -04:00
NGnius (Graham)
0019b7c073 Version bump to v1.3.0 2020-06-22 12:04:21 -04:00
NGnius (Graham)
78122ee445 Add automatic testing functionality 2020-06-17 21:04:40 -04:00
NGnius (Graham)
c912f3ba64 Add Client and Game OOP features (undocumented) 2020-06-17 21:04:08 -04:00
9b1e2548d1 Attempts to create custom block types
It can load certain assets (a Cube from a sample) but fails because of missing shaders
My own Cube doesn't even get that far
2020-06-14 21:40:47 +02:00
NGnius (Graham)
0d17a1b509 Update project references 2020-06-13 16:19:12 -04:00
NGnius (Graham)
960ab16f0b Merge branch 'master' of https://git.exmods.org/modtainers/GamecraftModdingAPI 2020-06-13 15:47:03 -04:00
NGnius (Graham)
d2c9cde1d2 Fix double event call, jankily 2020-06-13 15:26:31 -04:00
ae1f53e119 Add new blocks 2020-06-13 00:48:13 +02:00
62318b0843 Update to Gamecraft 2020.06.11.18.50
Removed some inputs from FakeInput
2020-06-12 17:27:36 +02:00
3dcce18ceb Add method to get selected blocks by player 2020-06-08 00:10:10 +02:00
448cf3af48 Expose the simulation-time object IDs
They are useful
2020-06-05 00:57:22 +02:00
cae626197f Implement Equals for the OOPs & fix Player properties
Fixed setting player properties
Changed player rotation to float3
Added constructor for BlockColor with an index param
Improved Player.Exists() ~~hopefully~~
2020-06-05 00:20:35 +02:00
NGnius (Graham)
cd78fd6718 Fix expired discord invite 2020-06-04 08:31:09 -04:00
f62211309e Added WaitForNextFrame() and fixed block scaling
A bit hacky, but it works
2020-06-04 01:42:13 +02:00
NGnius (Graham)
197fc5f2f9 Update twitter link 2020-06-03 14:37:11 -04:00
NGnius (Graham)
6a137472c1 Fix Block type exception on unsynced Specialize<T>() 2020-06-02 20:38:35 -04:00
5660bfc28d
Fix connected blocks not being detected in prefabs
Apparently they're processed, unlike in my save
2020-06-03 01:49:54 +02:00
dba7c0a46f
Added 2 block events and removed BDNEException
Added an event for placing and removing blocks
Added a class to wrap event calls in a try-catch
Removed BlockDoesNotExistException
Made Block.GetSimBody() return null if block doesn't exist
2020-06-02 03:15:32 +02:00
NGnius (Graham)
98c7e624f8 Add OOP Audio class 2020-05-29 21:30:53 -04:00
NGnius (Graham)
2d89b82759 Add player health and block-in-hand 2020-05-29 21:30:24 -04:00
0b8491cecf Move Sync() to properties and improve Block doc 2020-05-27 17:20:53 +02:00
269d30b0db Add support for connected bodies
Added center of mass
Removed delta velocities
2020-05-24 21:55:49 +02:00
ab7c5805fe Add object ID block and label support 2020-05-24 19:29:02 +02:00
dca6fe4c1b Fixes, added SimBody class 2020-05-23 00:06:49 +02:00
084cbb40c4 Added AsyncUtils and Block.PlaceNewAsync() 2020-05-22 03:00:33 +02:00
NGnius (Graham)
4e08acf44c Reduce potentially unnecessary calls to Sync() by always forcing Sync() for new blocks 2020-05-21 20:00:31 -04:00
NGnius (Graham)
e8f59e8641 Delay serializer registration and disable version check even in Debug mode to fix #5 2020-05-21 19:40:46 -04:00
NGnius (Graham)
8326d70cbf Integrate tweak and signal functionality into Blocks 2020-05-21 15:04:55 -04:00
NGnius (Graham)
fb841b6542 Add Timer and spawn point block properties 2020-05-20 20:42:02 -04:00
3ff98f29bb Improve GetBlockInfo(), block colors
Made GetBlockInfo() always return a reference without needing it as a parameter
Fixed Color property
Added CustomColor property for temporarily setting any color
2020-05-20 00:08:02 +02:00
NGnius (Graham)
97de5e606b Add first block children ConsoleBlock and TextBlock, I hope they're not bullied at school for those names 2020-05-17 23:36:11 -04:00
6dce87fb66 Documentation, added invalid block ID, error handling 2020-05-17 23:46:55 +02:00
1c5ce37fce Add debug interface API and improve block API
Added API for adding more information on the debug display (not object-oriented yet)
Removed the setter for block type to ensure stability
Made the block API return defaults if the block no longer exists
Added property to check if the block exists
Made a struct for the block's color property
Added missing block IDs
2020-05-17 23:46:31 +02:00
NGnius (Graham)
ebea9da232 Remove serializer registration at startup 2020-05-17 11:52:55 -04:00
NGnius (Graham)
500cb9b716 Fix groups stealing Ids 2020-05-16 12:53:58 -04:00
NGnius (Graham)
782e00e3dd Update deps 2020-05-15 17:42:04 -04:00
NGnius (Graham)
06b8c3664e Add Event builders 2020-05-15 17:42:04 -04:00
NGnius (Graham)
205b3fc51a Tweak exclusive group names 2020-05-15 17:42:04 -04:00
NGnius (Graham)
9cb6917d28 Create custom error types and error catching 2020-05-15 17:42:04 -04:00
6f8241554d Add block type and color properties 2020-05-15 17:42:04 -04:00
ff57a16565 Create Block class with existing functionality
Placement, movement, rotation, removal
Block looked at (in Player class), connected blocks
2020-05-15 17:42:04 -04:00
NGnius (Graham)
5168bfbad7 Document Player API 2020-05-15 17:42:04 -04:00
NGnius (Graham)
211c9c9c31 Add CommandBuilder existing commands function 2020-05-15 17:42:04 -04:00
NGnius (Graham)
e3b3fd5ef4 Implement Player OOP class 2020-05-15 17:42:04 -04:00
NGnius (Graham)
42b21bc16d Rework engine inheritance structure 2020-05-15 17:42:04 -04:00
NGnius (Graham)
a17e5a6449 Fix ref gen 2020-05-15 17:42:04 -04:00
NGnius (Graham)
bc77c35bdb Remove DiscordRPC 2020-05-15 17:42:04 -04:00
NGnius (Graham)
ac2d3b38c7 Remove IronPython 2020-05-15 17:42:04 -04:00
NGnius (Graham)
1f35b2a434 Upgrade to Harmony v2.0.0 2020-05-15 17:42:04 -04:00
NGnius (Graham)
f244eb9683 Fix script errors 2020-05-15 17:42:04 -04:00
NGnius (Graham)
4e5cc40be4 Convert Automation to something actually cross-platform 2020-05-15 17:42:04 -04:00
NGnius (Graham)
5cb27b05c0 Create CommandBuilder 2020-05-15 17:42:04 -04:00
NGnius (Graham)
934b542ea1 Fix build errors from GC update 2020-05-14 12:42:48 -04:00
NGnius (Graham)
1faa9898f7 Version 0.2.2 2020-04-29 12:07:06 -04:00
NGnius (Graham)
36516ec185 Add Persistence documentation 2020-04-28 22:59:36 -04:00
NGnius (Graham)
07ba6f2dc4 Add game file persistence functionality 2020-04-28 21:56:34 -04:00
83427b806e
Update to Gamecraft 2020.04.27.14.21 2020-04-28 15:59:22 +02:00
NGnius
2963517764 Merge pull request 'Add API to remove blocks, change PlaceBlock return type and others' (#4) from removal into master 2020-04-13 19:44:12 +00:00
9609187cff
Created a BlockUtility class, changed PlaceBlock return type
Removed GameState methods from block APIs
Created a BlockUtility class to get the block the player is looking at
Changed the return type of PlaceBlock, returning the ID of the newly placed block or null
2020-04-13 02:21:36 +02:00
81f2f613f7
Add API to remove blocks 2020-04-13 01:31:06 +02:00
f231ea9f6d
Fix placement engine for latest GC and add block IDs 2020-04-09 01:14:21 +02:00
NGnius
d1c0556b9c Update to Gamecraft 2020.04.06.14.50 2020-04-07 13:05:00 -04:00
NGnius
a9322a08dd Add semi-functional action inputs 2020-04-07 11:47:32 -04:00
NGnius
5ca18d272a Add WIP GUI input functionality 2020-04-04 14:48:12 -04:00
NGnius
d00bdc80ed Improve type safety of event types and version bump 2020-04-02 13:08:05 -04:00
NGnius
2149458d96 Add inventory select block support 2020-04-02 09:50:30 -04:00
NGnius
5654c041c5 Fix LatestBlockID 2020-03-12 19:29:20 -04:00
NGnius
206ec19d1e Merge branch 'master' of https://git.exmods.org/modtainers/GamecraftModdingAPI 2020-03-12 18:52:02 -04:00
NGnius
e29c5fb107 Merge branch 'master' of NorbiPeti/GamecraftModdingAPI into master 2020-03-12 22:51:53 +00:00
b4e70fcebe
Update to GC 2020.03.11.11.02
Fixed compile issues and added new blocks
2020-03-12 23:38:32 +01:00
NGnius
c6f8afa812 Add uREPL passthru for calling in-game commands 2020-03-06 10:52:07 -05:00
NGnius
c1bb863578 Add links to documentation 2020-03-04 18:05:15 -05:00
NGnius
fd98879ccd Add Tweakable stat support 2020-02-29 13:20:03 -05:00
NGnius
7ac5120ef5 Add Simulation and Build switch to events 2020-02-25 22:19:22 -05:00
NGnius
7519bc37ae Improve documentation 2020-02-25 18:05:13 -05:00
NGnius
feab9e38c9 Remove the thing 2020-02-25 18:05:02 -05:00
170 changed files with 16960 additions and 3027 deletions

View file

@ -1,66 +0,0 @@
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace Automation
{
class Automation
{
private static readonly string Name = "automation";
private static readonly string XmlPrefix = "<!--Start Dependencies-->\n <ItemGroup>\n";
private static readonly string XmlSuffix = " </ItemGroup>\n<!--End Dependencies-->";
private static readonly string GamecraftModdingAPI_csproj = @"\GamecraftModdingAPI.csproj";
static void Main(string[] args)
{
if (args.Length != 2 || args[0] == "-h" || args[0] == "--help")
{
Console.WriteLine($"Usage : {Name}.exe <path to Gamecraft DLLs> <path to GamecraftModdingAPI.csproj>");
Console.WriteLine("Other arguments:");
Console.WriteLine(" --help : display this message");
Console.WriteLine($" --version : display {Name}'s version");
return;
}
Console.WriteLine("Building Assembly references");
string asmXml = BuildAssemblyReferencesXML(args[0]);
//Console.WriteLine(asmXml);
string csprojPath = args[1].EndsWith(GamecraftModdingAPI_csproj)? args[1] : args[1]+GamecraftModdingAPI_csproj;
Console.WriteLine($"Parsing {csprojPath}");
string csprojContents = File.ReadAllText(csprojPath);
Match dependenciesStart = (new Regex(@"<\s*!--\s*Start\s+Dependencies\s*--\s*>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline)).Match(csprojContents);
Match dependenciesEnd = (new Regex(@"<\s*!--\s*End\s+Dependencies\s*--\s*>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline)).Match(csprojContents);
if (!dependenciesEnd.Success || !dependenciesStart.Success)
{
Console.WriteLine("Unable to find dependency XML comments, aborting!");
return;
}
csprojContents = csprojContents.Substring(0, dependenciesStart.Index) + asmXml + csprojContents.Substring(dependenciesEnd.Index+dependenciesEnd.Length);
//Console.WriteLine(csprojContents);
Console.WriteLine($"Writing Assembly references into {csprojPath}");
File.WriteAllText(csprojPath, csprojContents);
Console.WriteLine("Successfully generated Gamecraft assembly DLL references");
}
static string BuildAssemblyReferencesXML(string path)
{
StringBuilder result = new StringBuilder();
result.Append(XmlPrefix);
string[] files = Directory.GetFiles(path, "*.dll");
for(int i = 0; i<files.Length; i++)
{
if (files[i].Contains(@"\System.") || files[i].EndsWith(@"mscorlib.dll") || files[i].Contains(@"\Mono."))
{
// no
}
else
{
result.Append($" <Reference Include=\"{files[i].Substring(files[i].LastIndexOf(@"\") + 1).Replace(".dll", "")}\">\n <HintPath>{files[i]}</HintPath>\n </Reference>\n");
}
}
result.Append(XmlSuffix);
return result.ToString();
}
}
}

View file

@ -1,8 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>

67
Automation/bump_version.py Executable file
View file

@ -0,0 +1,67 @@
#!/usr/bin/python3
import argparse
import re
# this assumes a mostly semver-complient version number
if __name__ == "__main__":
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).")
args = parser.parse_args()
version_index = -1
try:
version_index = int(args.version)
except Exception:
if args.version.lower() == "major":
version_index = 0
elif args.version.lower() == "minor":
version_index = 1
elif args.version.lower() == "patch":
version_index = 2
if version_index < 0:
print("Could not parse version argument.")
exit(version_index)
print(version_index)
old_version = ""
new_version = ""
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "r") as xmlFile:
print("Parsing TechbloxModdingAPI.csproj")
fileStr = xmlFile.read()
versionMatch = re.search(r"<Version>(.+)</Version>", fileStr)
if versionMatch is None:
print("Unable to find version number in TechbloxModdingAPI.csproj")
exit(1)
old_version = versionMatch.group(1)
versionList = old_version.split(".")
if len(versionList) <= version_index:
print("Invalid version string")
exit(1)
versionList[version_index] = str(int(versionList[version_index]) + 1)
for i in range(version_index + 1, len(versionList)):
try:
int(versionList[i])
versionList[i] = "0"
except Exception:
tmp = versionList[i].split("-")
tmp[0] = "0"
versionList[i] = "-".join(tmp)
new_version = ".".join(versionList)
print(new_version)
newFileContents = fileStr.replace("<Version>"+old_version+"</Version>", "<Version>"+new_version+"</Version>")
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "w") as xmlFile:
print("Writing new version to project file")
xmlFile.write(newFileContents)
with open("../doxygen.conf", "r") as doxFile:
print("Parsing doxygen.conf")
doxStr = doxFile.read()
newFileContents = doxStr.replace("= \"v" + old_version + "\"", "= \"v" + new_version + "\"")
with open("../doxygen.conf", "w") as doxFile:
print("Writing new version to doxygen config")
doxFile.write(newFileContents)

62
Automation/gen_csproj.py Executable file
View file

@ -0,0 +1,62 @@
#!/usr/bin/python3
import argparse
from pathlib import Path, PurePath
import re
import os
DLL_EXCLUSIONS_REGEX = r"(System|Microsoft|Mono|IronPython|DiscordRPC|IllusionInjector|IllusionPlugin|netstandard)\."
def getAssemblyReferences(path):
asmDir = Path(path)
result = list()
addedPath = ""
if not asmDir.exists():
addedPath = "../"
asmDir = Path(addedPath + path)
for child in asmDir.iterdir():
if child.is_file() and re.search(DLL_EXCLUSIONS_REGEX, str(child)) is None and str(child).lower().endswith(".dll"):
childstr = str(child)
childstr = os.path.relpath(childstr, addedPath).replace("\\", "/")
result.append(childstr)
result.sort(key=str.lower)
result = [path + "/IllusionInjector.dll", path + "/IllusionPlugin.dll"] + result # Always put it on top
return result
def buildReferencesXml(path):
assemblyPathes = getAssemblyReferences(path)
result = list()
for asm in assemblyPathes:
asmPath = str(asm)
xml = " <Reference Include=\"" + asmPath[asmPath.rfind("/") + 1:].replace(".dll", "") + "\">\n" \
+ " <HintPath>" + asmPath.replace("/", "\\") + "</HintPath>\n" \
+ " <HintPath>..\\" + asmPath.replace("/", "\\") + "</HintPath>\n" \
+ " </Reference>\n"
result.append(xml)
return "<!--Start Dependencies-->\n <ItemGroup>\n" + "".join(result) + " </ItemGroup>\n<!--End Dependencies-->"
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate TechbloxModdingAPI.csproj")
# TODO (maybe?): add params for custom csproj read and write locations
args = parser.parse_args()
print("Building Assembly references")
asmXml = buildReferencesXml("../ref_TB/Techblox_Data/Managed")
# print(asmXml)
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "r") as xmlFile:
print("Parsing TechbloxModdingAPI.csproj")
fileStr = xmlFile.read()
# print(fileStr)
depsStart = re.search(r"\<!--\s*Start\s+Dependencies\s*--\>", fileStr)
depsEnd = re.search(r"\<!--\s*End\s+Dependencies\s*--\>", fileStr)
if depsStart is None or depsEnd is None:
print("Unable to find dependency XML comments, aborting!")
exit(1)
newFileStr = fileStr[:depsStart.start() - 1] + "\n" + asmXml + "\n" + fileStr[depsEnd.end() + 1:]
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "w") as xmlFile:
print("Writing Assembly references")
xmlFile.write(newFileStr)
# print(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>

View file

@ -1,31 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29411.108
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GamecraftModdingAPI", "GamecraftModdingAPI\GamecraftModdingAPI.csproj", "{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Automation", "Automation\Automation.csproj", "{1DF6E538-4DA4-4708-A567-781AF788921A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Release|Any CPU.Build.0 = Release|Any CPU
{1DF6E538-4DA4-4708-A567-781AF788921A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1DF6E538-4DA4-4708-A567-781AF788921A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1DF6E538-4DA4-4708-A567-781AF788921A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1DF6E538-4DA4-4708-A567-781AF788921A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {72FB94D0-6C50-475B-81E0-C94C7D7A2A17}
EndGlobalSection
EndGlobal

View file

@ -1,20 +0,0 @@
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// Preset block colours
/// </summary>
public enum BlockColors
{
Default = byte.MaxValue,
White = 0,
Pink,
Purple,
Blue,
Aqua,
Green,
Lime,
Yellow,
Orange,
Red
}
}

View file

@ -1,186 +0,0 @@
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// Possible block types
/// </summary>
public enum BlockIDs
{
AluminiumCube,
AxleS,
HingeS = 3,
MotorS,
HingeM,
MotorM,
TyreM,
AxleM,
IronCube,
RubberCube,
OiledCube,
AluminiumConeSegment, //12
AluminiumCorner,
AluminiumRoundedCorner,
AluminiumSlicedCube,
AluminiumRoundedSlicedCube,
AluminiumCylinder,
AluminiumPyramidSegment,
AluminiumSlope,
AluminiumRoundedSlope,
AluminiumSphere,
RubberConeSegment, //22
RubberCorner,
RubberRoundedCorner,
RubberSlicedCube,
RubberRoundedSlicedCube,
RubberCylinder,
RubberPyramidSegment,
RubberSlope,
RubberRoundedSlope,
RubberSphere,
OiledConeSegment, //32
OiledCorner,
OiledRoundedCorner,
OiledSlicedCube,
OiledRoundedSlicedCube,
OiledCylinder,
OiledPyramidSegment,
OiledSlope,
OiledRoundedSlope,
OiledSphere,
IronConeSegment, //42
IronCorner,
IronRoundedCorner,
IronSlicedCube,
IronRoundedSlicedCube,
IronCylinder,
IronPyramidSegment,
IronSlope,
IronRoundedSlope,
IronSphere,
GlassCube, //52
GlassSlicedCube,
GlassSlope,
GlassCorner,
GlassPyramidSegment,
GlassRoundedSlicedCube,
GlassRoundedSlope,
GlassRoundedCorner,
GlassConeSegment,
GlassCylinder,
GlassSphere,
Lever, //63 - two IDs skipped
PlayerSpawn = 66, //Crashes without special handling
SmallSpawn,
MediumSpawn,
LargeSpawn,
BallJoint,
UniversalJoint,
ServoAxle,
ServoHinge,
StepperAxle,
StepperHinge,
TelescopicJoint,
DampedSpring,
ServoPiston,
StepperPiston,
PneumaticPiston,
PneumaticHinge,
PneumaticAxle, //82
PilotSeat = 90, //Might crash
PassengerSeat,
PilotControls,
GrassCube,
DirtCube,
GrassConeSegment,
GrassCorner,
GrassRoundedCorner,
GrassSlicedCube,
GrassRoundedSlicedCube,
GrassPyramidSegment,
GrassSlope,
GrassRoundedSlope,
DirtConeSegment,
DirtCorner,
DirtRoundedCorner,
DirtSlicedCube,
DirtRoundedSlicedCube,
DirtPyramidSegment,
DirtSlope,
DirtRoundedSlope,
RubberHemisphere,
AluminiumHemisphere,
GrassInnerCornerBulged,
DirtInnerCornerBulged,
IronHemisphere,
OiledHemisphere,
GlassHemisphere,
TyreS,
ThreeWaySwitch,
Dial, //120
CharacterOnEnterTrigger, //Probably crashes
CharacterOnLeaveTrigger,
CharacterOnStayTrigger,
ObjectOnEnterTrigger,
ObjectOnLeaveTrigger,
ObjectOnStayTrigger,
Button,
Switch,
TextBlock, //Brings up a screen
ConsoleBlock, //Brings up a screen
Door,
GlassDoor,
PoweredDoor,
PoweredGlassDoor,
AluminiumTubeCorner,
IronTubeCorner,
WoodCube,
WoodSlicedCube,
WoodSlope,
WoodCorner,
WoodPyramidSegment,
WoodConeSegment,
WoodRoundedSlicedCube,
WoodRoundedSlope,
WoodRoundedCorner,
WoodCylinder,
WoodHemisphere,
WoodSphere,
BrickCube, //149
BrickSlicedCube = 151,
BrickSlope,
BrickCorner,
ConcreteCube,
ConcreteSlicedCube,
ConcreteSlope,
ConcreteCorner,
RoadCarTyre,
OffRoadCarTyre,
RacingCarTyre,
BicycleTyre,
FrontBikeTyre,
RearBikeTyre,
ChopperBikeTyre,
TractorTyre,
MonsterTruckTyre,
MotocrossBikeTyre,
CartTyre,
BeachTree1 = 200,
BeachTree2,
BeachTree3,
Rock1,
Rock2,
Rock3,
Rock4,
BirchTree1,
BirchTree2,
BirchTree3,
PineTree1,
PineTree2,
PineTree3,
Flower1,
Flower2,
Flower3,
Shrub1,
Shrub2,
Shrub3
}
}

View file

@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using RobocraftX.Common;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// ExclusiveGroups and IDs used with blocks
/// </summary>
public static class BlockIdentifiers
{
/// <summary>
/// Blocks placed by the player
/// </summary>
public static ExclusiveGroup OWNED_BLOCKS { get { return CommonExclusiveGroups.OWNED_BLOCKS_GROUP; } }
/// <summary>
/// Extra parts used in functional blocks
/// </summary>
public static ExclusiveGroup FUNCTIONAL_BLOCK_PARTS { get { return CommonExclusiveGroups.FUNCTIONAL_BLOCK_PART_GROUP; } }
/// <summary>
/// Blocks which are disabled in Simulation mode
/// </summary>
public static ExclusiveGroup SIM_BLOCKS_DISABLED { get { return CommonExclusiveGroups.BLOCKS_DISABLED_IN_SIM_GROUP; } }
public static ExclusiveGroup SPAWN_POINTS { get { return CommonExclusiveGroups.SPAWN_POINTS_GROUP; } }
public static ExclusiveGroup SPAWN_POINTS_DISABLED { get { return CommonExclusiveGroups.SPAWN_POINTS_DISABLED_GROUP; } }
public static ExclusiveGroup OUTPUT_SIGNAL_CHANNELS { get { return CommonExclusiveGroups.CHANNEL_OUTPUT_SIGNAL_GROUPS; } }
/// <summary>
/// The ID of the most recently placed block
/// </summary>
public static uint LatestBlockID { get { return CommonExclusiveGroups.CurrentBlockEntityID - 1; } }
}
}

View file

@ -1,55 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity.Mathematics;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// Common block movement operations
/// </summary>
public static class Movement
{
private static MovementEngine movementEngine = new MovementEngine();
/// <summary>
/// Move a single block by a specific (x,y,z) amount
/// </summary>
/// <param name="id">The block's id</param>
/// <param name="vector">The movement amount (x,y,z)</param>
/// <returns>Whether the operation was successful</returns>
public static bool MoveBlock(uint id, float3 vector)
{
if (movementEngine.IsInGame && movementEngine.IsBuildMode())
{
movementEngine.MoveBlock(id, vector);
return true;
}
return false;
}
/// <summary>
/// Move all connected blocks by a specific (x,y,z) amount
/// </summary>
/// <param name="id">The starting block's id</param>
/// <param name="vector">The movement amount (x,y,z)</param>
/// <returns>Whether the operation was successful</returns>
public static bool MoveConnectedBlocks(uint id, float3 vector)
{
if (movementEngine.IsInGame && movementEngine.IsBuildMode())
{
movementEngine.MoveConnectedBlocks(id, vector);
return true;
}
return false;
}
public static void Init()
{
GamecraftModdingAPI.Utility.GameEngineManager.AddGameEngine(movementEngine);
}
}
}

View file

@ -1,88 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RobocraftX;
using RobocraftX.Blocks;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Common;
using RobocraftX.Multiplayer;
using RobocraftX.SimulationModeState;
using RobocraftX.UECS;
using Unity.Entities;
using Svelto.Context;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Transforms;
using Unity.Mathematics;
using UnityEngine;
using GamecraftModdingAPI.Utility;
using Svelto.DataStructures;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// Engine which executes block movement actions
/// </summary>
public class MovementEngine : IApiEngine
{
public string Name { get; } = "GamecraftModdingAPIMovementGameEngine";
public IEntitiesDB entitiesDB { set; private get; }
public bool IsInGame = false;
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
// implementations for Movement static class
public float3 MoveBlock(uint blockID, float3 vector)
{
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
// main (persistent) position
posStruct.position += vector;
// placement grid position
gridStruct.position += vector;
// rendered position
transStruct.position += vector;
// collision position
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Translation
{
Value = posStruct.position
});
return posStruct.position;
}
public float3 MoveConnectedBlocks(uint blockID, float3 vector)
{
Stack<uint> cubeStack = new Stack<uint>();
FasterList<uint> cubesToMove = new FasterList<uint>();
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubesToMove, (in GridConnectionsEntityStruct g) => { return false; });
for (int i = 0; i < cubesToMove.count; i++)
{
MoveBlock(cubesToMove[i], vector);
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(cubesToMove[i], CommonExclusiveGroups.OWNED_BLOCKS_GROUP).isProcessed = false;
}
return this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP).position;
}
public bool IsBuildMode()
{
return GamecraftModdingAPI.Utility.GameState.IsBuildMode();
}
}
}

View file

@ -1,57 +0,0 @@
using System;
using Unity.Mathematics;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// Common block placement operations
/// </summary>
public static class Placement
{
private static PlacementEngine placementEngine = new PlacementEngine();
/// <summary>
/// Place a block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position.
/// Place blocks next to each other to connect them.
/// </summary>
/// <param name="block">The block's type</param>
/// <param name="color">The block's color</param>
/// <param name="darkness">The block color's darkness (0-9) - 0 is default color</param>
/// <param name="position">The block's position in the grid - default block size is 0.2</param>
/// <param name="rotation">The block's rotation in degrees</param>
/// <param name="uscale">The block's uniform scale - default scale is 1 (with 0.2 width)</param>
/// <param name="scale">The block's non-uniform scale - 0 means <paramref name="uscale"/> is used</param>
/// <param name="playerId">The player who placed the block</param>
/// <returns>Whether the operation was successful</returns>
public static bool PlaceBlock(BlockIDs block, float3 position,
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, uint playerId = 0)
{
if (placementEngine.IsInGame && GameState.IsBuildMode())
{
try
{
placementEngine.PlaceBlock(block, color, darkness, position, uscale, scale, playerId, rotation);
}
catch (Exception e)
{
uREPL.Log.Output(e.Message);
#if DEBUG
//Logging.LogException(e);
#endif
return false;
}
return true;
}
return false;
}
public static void Init()
{
GameEngineManager.AddGameEngine(placementEngine);
}
}
}

View file

@ -1,142 +0,0 @@
using System;
using System.Reflection;
using DataLoader;
using Harmony;
using RobocraftX.Blocks;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Blocks.Scaling;
using RobocraftX.Character;
using RobocraftX.CommandLine.Custom;
using RobocraftX.Common;
using RobocraftX.Common.Input;
using RobocraftX.Common.Utilities;
using RobocraftX.CR.MachineEditing;
using RobocraftX.StateSync;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using uREPL;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// Engine which executes block placement actions
/// </summary>
public class PlacementEngine : IApiEngine
{
public bool IsInGame = false;
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
public IEntitiesDB entitiesDB { get; set; }
internal static BlockEntityFactory _blockEntityFactory; //Injected from PlaceBlockEngine
public void PlaceBlock(BlockIDs block, BlockColors color, byte darkness, float3 position, int uscale,
float3 scale, uint playerId, float3 rotation)
{ //It appears that only the non-uniform scale has any visible effect, but if that's not given here it will be set to the uniform one
if (darkness > 9)
throw new Exception("That is too dark. Make sure to use 0-9 as darkness. (0 is default.)");
BuildBlock((ushort) block, (byte) (color + darkness * 10), position, uscale, scale, rotation).Init(
new BlockPlacementInfoStruct()
{
loadedFromDisk = false,
placedBy = playerId
});
}
private EntityStructInitializer BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot)
{
if (_blockEntityFactory == null)
throw new Exception("The factory is null.");
if (uscale < 1)
throw new Exception("Scale needs to be at least 1");
if (scale.x < 4e-5) scale.x = uscale;
if (scale.y < 4e-5) scale.y = uscale;
if (scale.z < 4e-5) scale.z = uscale;
uint dbid = block;
if (!PrefabsID.DBIDMAP.ContainsKey(dbid))
throw new Exception("Block with ID " + dbid + " not found!");
//RobocraftX.CR.MachineEditing.PlaceBlockEngine
ScalingEntityStruct scaling = new ScalingEntityStruct {scale = scale};
Quaternion rotQ = Quaternion.Euler(rot);
RotationEntityStruct rotation = new RotationEntityStruct {rotation = rotQ};
GridRotationStruct gridRotation = new GridRotationStruct
{position = position, rotation = rotQ};
CubeCategoryStruct category = new CubeCategoryStruct
{category = CubeCategory.General, type = CubeType.Block};
DBEntityStruct dbEntity = new DBEntityStruct {DBID = dbid};
uint num = PrefabsID.DBIDMAP[dbid];
GFXPrefabEntityStructGO gfx = new GFXPrefabEntityStructGO {prefabID = num};
BlockPlacementScaleEntityStruct placementScale = new BlockPlacementScaleEntityStruct
{
blockPlacementHeight = uscale, blockPlacementWidth = uscale, desiredScaleFactor = uscale,
snapGridScale = uscale,
unitSnapOffset = 0, isUsingUnitSize = true
};
EquippedColourStruct colour = new EquippedColourStruct {indexInPalette = color};
EGID egid2;
switch (category.category)
{
case CubeCategory.SpawnPoint:
case CubeCategory.BuildingSpawnPoint:
egid2 = MachineEditingGroups.NewSpawnPointBlockID;
break;
default:
egid2 = MachineEditingGroups.NewBlockID;
break;
}
int cubeId = PrefabsID.GenerateDBID((ushort) category.category, block);
EntityStructInitializer
structInitializer =
_blockEntityFactory.Build(egid2, (uint) cubeId); //The ghost block index is only used for triggers
if (colour.indexInPalette != byte.MaxValue)
structInitializer.Init(new ColourParameterEntityStruct
{
indexInPalette = colour.indexInPalette
});
structInitializer.Init(new GFXPrefabEntityStructGPUI(gfx.prefabID));
structInitializer.Init(new PhysicsPrefabEntityStruct(gfx.prefabID));
structInitializer.Init(dbEntity);
structInitializer.Init(new PositionEntityStruct {position = position});
structInitializer.Init(rotation);
structInitializer.Init(scaling);
structInitializer.Init(gridRotation);
structInitializer.Init(new UniformBlockScaleEntityStruct
{
scaleFactor = placementScale.desiredScaleFactor
});
return structInitializer;
}
public string Name { get; } = "GamecraftModdingAPIPlacementGameEngine";
[HarmonyPatch]
public class FactoryObtainerPatch
{
static void Postfix(BlockEntityFactory blockEntityFactory)
{
_blockEntityFactory = blockEntityFactory;
Logging.MetaDebugLog("Block entity factory injected.");
}
static MethodBase TargetMethod(HarmonyInstance instance)
{
return typeof(PlaceBlockEngine).GetConstructors()[0];
}
}
}
}

View file

@ -1,55 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity.Mathematics;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// Common block movement operations
/// </summary>
public static class Rotation
{
private static RotationEngine rotationEngine = new RotationEngine();
/// <summary>
/// Rotate a single block by a specific amount in degrees
/// </summary>
/// <param name="id">The block's id</param>
/// <param name="vector">The rotation amount around the x,y,z-axis</param>
/// <returns>Whether the operation was successful</returns>
public static bool RotateBlock(uint id, float3 vector)
{
if (rotationEngine.IsInGame && rotationEngine.IsBuildMode())
{
rotationEngine.RotateBlock(id, vector);
return true;
}
return false;
}
/// <summary>
/// Rotate all connected blocks by a specific amount in degrees
/// </summary>
/// <param name="id">The starting block's id</param>
/// <param name="vector">The rotation around the x,y,z-axis</param>
/// <returns>Whether the operation was successful</returns>
public static bool RotateConnectedBlocks(uint id, float3 vector)
{
if (rotationEngine.IsInGame && rotationEngine.IsBuildMode())
{
rotationEngine.RotateConnectedBlocks(id, vector);
return true;
}
return false;
}
public static void Init()
{
GamecraftModdingAPI.Utility.GameEngineManager.AddGameEngine(rotationEngine);
}
}
}

View file

@ -1,86 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RobocraftX;
using RobocraftX.Blocks;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Common;
using RobocraftX.Multiplayer;
using RobocraftX.SimulationModeState;
using RobocraftX.UECS;
using Unity.Entities;
using Svelto.Context;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Transforms;
using Unity.Mathematics;
using UnityEngine;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// Engine which executes block movement actions
/// </summary>
public class RotationEngine : IApiEngine
{
public string Name { get; } = "GamecraftModdingAPIRotationGameEngine";
public IEntitiesDB entitiesDB { set; private get; }
public bool IsInGame = false;
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
// implementations for Movement static class
public float3 RotateBlock(uint blockID, Vector3 vector)
{
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntity<RotationEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
// main (persistent) position
Quaternion newRotation = (Quaternion)rotStruct.rotation;
newRotation.eulerAngles += vector;
rotStruct.rotation = (quaternion)newRotation;
// placement grid rotation
Quaternion newGridRotation = (Quaternion)gridStruct.rotation;
newGridRotation.eulerAngles += vector;
gridStruct.rotation = (quaternion)newGridRotation;
// rendered position
Quaternion newTransRotation = (Quaternion)rotStruct.rotation;
newTransRotation.eulerAngles += vector;
transStruct.rotation = newTransRotation;
// collision position
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Unity.Transforms.Rotation
{
Value = rotStruct.rotation
});
return ((Quaternion)rotStruct.rotation).eulerAngles;
}
public float3 RotateConnectedBlocks(uint blockID, Vector3 vector)
{
throw new NotImplementedException();
}
public bool IsBuildMode()
{
return GamecraftModdingAPI.Utility.GameState.IsBuildMode();
}
}
}

View file

@ -1,182 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RobocraftX;
using RobocraftX.Blocks;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Common;
using RobocraftX.Multiplayer;
using RobocraftX.SimulationModeState;
using RobocraftX.UECS;
using Unity.Entities;
using Svelto.Context;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Transforms;
using Unity.Mathematics;
using UnityEngine;
using Gamecraft.Wires;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// Engine which executes signal actions
/// </summary>
public class SignalEngine : IApiEngine
{
public string Name { get; } = "GamecraftModdingAPISignalGameEngine";
public IEntitiesDB entitiesDB { set; private get; }
public bool IsInGame = false;
private System.Random random = new System.Random();
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
public void DoTheThing()
{
GamecraftModdingAPI.Tasks.Repeatable thing = new GamecraftModdingAPI.Tasks.Repeatable(
() => { Thing(); },
() => { return IsSimulationMode(); } );
GamecraftModdingAPI.Tasks.Scheduler.Schedule(thing);
}
public void Thing()
{
uint count = 0;
EGID[] eBlocks = GetElectricBlocks();
for (uint i = 0u; i < eBlocks.Length; i++)
{
uint[] ids = GetSignalIDs(eBlocks[i]);
for (uint j = 0u; j < ids.Length; j++)
{
SetSignal(ids[j], (float)random.NextDouble());
count++;
}
}
Logging.Log($"Did the thing on {count} inputs");
}
// implementations for Signal static class
public bool SetSignal(EGID blockID, float signal, out uint signalID, bool input = true)
{
signalID = GetSignalIDs(blockID, input)[0];
return SetSignal(signalID, signal);
}
public bool SetSignal(uint signalID, float signal, bool input = true)
{
ExclusiveGroup group = input ? NamedExclusiveGroup<InputPortsGroup>.Group : NamedExclusiveGroup<OutputPortsGroup>.Group;
if (entitiesDB.Exists<PortEntityStruct>(signalID, group))
{
entitiesDB.QueryEntity<PortEntityStruct>(signalID, group).value = signal;
return true;
}
return false;
}
public float AddSignal(EGID blockID, float signal, out uint signalID, bool clamp = true, bool input = true)
{
signalID = GetSignalIDs(blockID, input)[0];
return AddSignal(signalID, signal, clamp, input);
}
public float AddSignal(uint signalID, float signal, bool clamp = true, bool input = true)
{
ExclusiveGroup group = input ? NamedExclusiveGroup<InputPortsGroup>.Group : NamedExclusiveGroup<OutputPortsGroup>.Group;
if (entitiesDB.Exists<PortEntityStruct>(signalID, group))
{
ref PortEntityStruct pes = ref entitiesDB.QueryEntity<PortEntityStruct>(signalID, group);
pes.value += signal;
if (clamp)
{
if (pes.value > Signals.POSITIVE_HIGH)
{
pes.value = Signals.POSITIVE_HIGH;
}
else if (pes.value < Signals.NEGATIVE_HIGH)
{
pes.value = Signals.NEGATIVE_HIGH;
}
return pes.value;
}
}
return signal;
}
public float GetSignal(EGID blockID, out uint signalID, bool input = true)
{
signalID = GetSignalIDs(blockID, input)[0];
return GetSignal(signalID, input);
}
public float GetSignal(uint signalID, bool input = true)
{
ExclusiveGroup group = input ? NamedExclusiveGroup<InputPortsGroup>.Group : NamedExclusiveGroup<OutputPortsGroup>.Group;
if (entitiesDB.Exists<PortEntityStruct>(signalID, group))
{
return entitiesDB.QueryEntity<PortEntityStruct>(signalID, group).value;
}
return 0f;
}
public uint[] GetSignalIDs(EGID blockID, bool input = true)
{
ref BlockPortsStruct bps = ref entitiesDB.QueryEntity<BlockPortsStruct>(blockID);
uint[] signals;
if (input) {
signals = new uint[bps.inputCount];
for (uint i = 0u; i < bps.inputCount; i++)
{
signals[i] = bps.firstInputID + i;
}
} else {
signals = new uint[bps.outputCount];
for (uint i = 0u; i < bps.outputCount; i++)
{
signals[i] = bps.firstOutputID + i;
}
}
return signals;
}
public EGID[] GetElectricBlocks()
{
uint count = entitiesDB.Count<BlockPortsStruct>(BlockIdentifiers.OWNED_BLOCKS) + entitiesDB.Count<BlockPortsStruct>(BlockIdentifiers.FUNCTIONAL_BLOCK_PARTS);
uint i = 0;
EGID[] res = new EGID[count];
foreach (ref BlockPortsStruct s in entitiesDB.QueryEntities<BlockPortsStruct>(BlockIdentifiers.OWNED_BLOCKS))
{
res[i] = s.ID;
i++;
}
foreach (ref BlockPortsStruct s in entitiesDB.QueryEntities<BlockPortsStruct>(BlockIdentifiers.FUNCTIONAL_BLOCK_PARTS))
{
res[i] = s.ID;
i++;
}
return res;
}
public bool IsSimulationMode()
{
return GamecraftModdingAPI.Utility.GameState.IsSimulationMode();
}
}
}

View file

@ -1,182 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks
{
/// <summary>
/// [EXPERIMENTAL] Common block signal operations
/// </summary>
public static class Signals
{
// Signal constants
public static readonly float HIGH = 1.0f;
public static readonly float POSITIVE_HIGH = HIGH;
public static readonly float NEGATIVE_HIGH = -1.0f;
public static readonly float LOW = 0.0f;
private static SignalEngine signalEngine = new SignalEngine();
/// <summary>
/// Set the electric block's (first) signal value.
/// </summary>
/// <param name="blockID">The block's id.</param>
/// <param name="signal">The signal value (-1 to 1; not enforced).</param>
/// <param name="input">Whether to retrieve input IDs (true) or output IDs (false).</param>
/// <param name="owned">Whether the block is in the owned group (true) or functional group (false)</param>
public static void SetSignalByBlock(uint blockID, float signal, bool input = true, bool owned = true)
{
EGID egid = new EGID(blockID, owned ? BlockIdentifiers.OWNED_BLOCKS : BlockIdentifiers.FUNCTIONAL_BLOCK_PARTS);
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
signalEngine.SetSignal(egid, signal, out uint _, input);
}
}
public static void SetSignalByBlock(EGID blockID, float signal, bool input = true)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
signalEngine.SetSignal(blockID, signal, out uint _, input);
}
}
/// <summary>
/// Set a signal's value.
/// </summary>
/// <param name="signalID">The channel cluster's id.</param>
/// <param name="signal">The signal value (-1 to 1; not enforced).</param>
/// <param name="input">Whether to retrieve input IDs (true) or output IDs (false).</param>
public static void SetSignalByID(uint signalID, float signal, bool input = true)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
signalEngine.SetSignal(signalID, signal, input);
}
}
/// <summary>
/// Add a value to an electric block's signal.
/// </summary>
/// <param name="blockID">The block's id.</param>
/// <param name="signal">The signal value to add.</param>
/// <param name="clamp">Whether to clamp the resulting signal value between -1 and 1.</param>
/// <param name="input">Whether to retrieve input IDs (true) or output IDs (false).</param>
/// <param name="owned">Whether the block is in the owned group (true) or functional group (false)</param>
/// <returns>The signal's new value.</returns>
public static float AddSignalByBlock(uint blockID, float signal, bool clamp = true, bool input = true, bool owned = true)
{
EGID egid = new EGID(blockID, owned ? BlockIdentifiers.OWNED_BLOCKS : BlockIdentifiers.FUNCTIONAL_BLOCK_PARTS);
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
return signalEngine.AddSignal(egid, signal, out uint _, clamp, input);
}
return 0f;
}
public static float AddSignalByBlock(EGID blockID, float signal, bool clamp = true, bool input = true)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
return signalEngine.AddSignal(blockID, signal, out uint _, clamp, input);
}
return 0f;
}
/// <summary>
/// Add a value to a conductive cluster channel.
/// </summary>
/// <param name="signalID">The channel cluster's id.</param>
/// <param name="signal">The signal value to add.</param>
/// <param name="clamp">Whether to clamp the resulting signal value between -1 and 1.</param>
/// <param name="input">Whether to retrieve input IDs (true) or output IDs (false).</param>
/// <returns>The signal's new value.</returns>
public static float AddSignalByID(uint signalID, float signal, bool clamp = true, bool input = true)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
return signalEngine.AddSignal(signalID, signal, clamp, input);
}
return 0f;
}
/// <summary>
/// Get a electric block's signal's (first) value.
/// </summary>
/// <param name="blockID">The block's id.</param>
/// <param name="input">Whether to retrieve input IDs (true) or output IDs (false).</param>
/// <param name="owned">Whether the block is in the owned group (true) or functional group (false)</param>
/// <returns>The signal's value.</returns>
public static float GetSignalByBlock(uint blockID, bool input = true, bool owned = true)
{
EGID egid = new EGID(blockID, owned? BlockIdentifiers.OWNED_BLOCKS : BlockIdentifiers.FUNCTIONAL_BLOCK_PARTS);
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
return signalEngine.GetSignal(egid, out uint _, input);
}
return 0f;
}
public static float GetSignalByBlock(EGID blockID, bool input = true)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
return signalEngine.GetSignal(blockID, out uint _, input);
}
return 0f;
}
/// <summary>
/// Get a signal's value.
/// </summary>
/// <param name="signalID">The signal's id.</param>
/// <param name="input">Whether to retrieve input IDs (true) or output IDs (false).</param>
/// <returns>The signal's value.</returns>
public static float GetSignalByID(uint signalID, bool input = true)
{
if (signalEngine.IsInGame && signalEngine.IsSimulationMode())
{
return signalEngine.GetSignal(signalID, input);
}
return 0f;
}
/// <summary>
/// Get the ID of every electric block in the game world.
/// </summary>
/// <returns>The block IDs.</returns>
public static EGID[] GetElectricBlocks()
{
return signalEngine.GetElectricBlocks();
}
/// <summary>
/// Get the unique identifiers for the input wires connected to an electric block.
/// </summary>
/// <param name="blockID">The block's id.</param>
/// <param name="input">Whether to retrieve input IDs (true) or output IDs (false).</param>
/// <param name="owned">Whether the block is in the owned group (true) or functional group (false)</param>
/// <returns>The unique IDs.</returns>
public static uint[] GetSignalIDs(uint blockID, bool input = true, bool owned = true)
{
EGID egid = new EGID(blockID, owned ? BlockIdentifiers.OWNED_BLOCKS : BlockIdentifiers.FUNCTIONAL_BLOCK_PARTS);
return signalEngine.GetSignalIDs(egid, input);
}
public static uint[] GetSignalIDs(EGID blockID, bool input = true, bool owned = true)
{
return signalEngine.GetSignalIDs(blockID, input);
}
public static void Init()
{
GameEngineManager.AddGameEngine(signalEngine);
}
}
}

View file

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Harmony;
using Svelto.Context;
using Svelto.ECS;
using RobocraftX;
using RobocraftX.Multiplayer;
using RobocraftX.StateSync;
using Unity.Entities;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Commands
{
/// <summary>
/// Patch of RobocraftX.GUI.CommandLine.CommandLineCompositionRoot.Compose<T>()
/// </summary>
// TODO: fix
[HarmonyPatch]
//[HarmonyPatch(typeof(RobocraftX.GUI.CommandLine.CommandLineCompositionRoot))]
//[HarmonyPatch("Compose")]
//[HarmonyPatch("Compose", new Type[] { typeof(UnityContext<FullGameCompositionRoot>), typeof(EnginesRoot), typeof(World), typeof(Action), typeof(MultiplayerInitParameters), typeof(StateSyncRegistrationHelper)})]
static class CommandPatch
{
public static void Postfix(object contextHolder, EnginesRoot enginesRoot, World physicsWorld, Action reloadGame, MultiplayerInitParameters multiplayerParameters, ref StateSyncRegistrationHelper stateSyncReg)
{
// When a game is loaded, register the command engines
CommandManager.RegisterEngines(enginesRoot);
}
public static MethodBase TargetMethod(HarmonyInstance instance)
{
return typeof(RobocraftX.GUI.CommandLine.CommandLineCompositionRoot).GetMethod("Compose").MakeGenericMethod(typeof(object));
//return func.Method;
}
}
}

View file

@ -1,60 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// Convenient factories for mod event engines
/// </summary>
public static class EventEngineFactory
{
/// <summary>
/// Factory method which automatically adds the SimpleEventHandlerEngine to the Manager
/// </summary>
/// <param name="name">The name of the engine</param>
/// <param name="type">The type of event to handle</param>
/// <param name="onActivated">The operation to do when the event is created</param>
/// <param name="onDestroyed">The operation to do when the event is destroyed (if applicable)</param>
/// <returns>The created object</returns>
public static SimpleEventHandlerEngine CreateAddSimpleHandler(string name, object type, Action onActivated, Action onDestroyed)
{
var engine = new SimpleEventHandlerEngine(onActivated, onDestroyed, type, name);
EventManager.AddEventHandler(engine);
return engine;
}
/// <summary>
/// Factory method which automatically adds the SimpleEventHandlerEngine to the Manager
/// </summary>
/// <param name="name">The name of the engine</param>
/// <param name="type">The type of event to handle</param>
/// <param name="onActivated">The operation to do when the event is created</param>
/// <param name="onDestroyed">The operation to do when the event is destroyed (if applicable)</param>
/// <returns>The created object</returns>
public static SimpleEventHandlerEngine CreateAddSimpleHandler(string name, object type, Action<IEntitiesDB> onActivated, Action<IEntitiesDB> onDestroyed)
{
var engine = new SimpleEventHandlerEngine(onActivated, onDestroyed, type, name);
EventManager.AddEventHandler(engine);
return engine;
}
/// <summary>
/// Factory method which automatically adds the SimpleEventEmitterEngine to the Manager
/// </summary>
/// <param name="name">The name of the engine</param>
/// <param name="type">The type of event to emit</param>
/// <param name="isRemovable">Will removing this engine not break your code?</param>
/// <returns>The created object</returns>
public static SimpleEventEmitterEngine CreateAddSimpleEmitter(string name, object type, bool isRemovable = true)
{
var engine = new SimpleEventEmitterEngine(type, name, isRemovable);
EventManager.AddEventEmitter(engine);
return engine;
}
}
}

View file

@ -1,120 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// Keeps track of event handlers and emitters.
/// This is used to add, remove and get API event handlers and emitters.
/// </summary>
public static class EventManager
{
private static Dictionary<string, IEventEmitterEngine> _eventEmitters = new Dictionary<string, IEventEmitterEngine>();
private static Dictionary<string, IEventHandlerEngine> _eventHandlers = new Dictionary<string, IEventHandlerEngine>();
private static EnginesRoot _lastEngineRoot;
// event handler management
public static void AddEventHandler(IEventHandlerEngine engine)
{
_eventHandlers[engine.Name] = engine;
if (_lastEngineRoot != null)
{
Logging.MetaDebugLog($"Registering IEventHandlerEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine);
}
}
public static bool ExistsEventHandler(string name)
{
return _eventHandlers.ContainsKey(name);
}
public static bool ExistsEventHandler(IEventHandlerEngine engine)
{
return ExistsEventHandler(engine.Name);
}
public static IEventHandlerEngine GetEventHandler(string name)
{
return _eventHandlers[name];
}
public static string[] GetEventHandlerNames()
{
return _eventHandlers.Keys.ToArray();
}
public static void RemoveEventHandler(string name)
{
_eventHandlers.Remove(name);
}
// event emitter management
public static void AddEventEmitter(IEventEmitterEngine engine)
{
_eventEmitters[engine.Name] = engine;
if (_lastEngineRoot != null)
{
Logging.MetaDebugLog($"Registering IEventEmitterEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine);
}
}
public static bool ExistsEventEmitter(string name)
{
return _eventEmitters.ContainsKey(name);
}
public static bool ExistsEventEmitter(IEventEmitterEngine engine)
{
return ExistsEventEmitter(engine.Name);
}
public static IEventEmitterEngine GetEventEmitter(string name)
{
return _eventEmitters[name];
}
public static string[] GetEventEmitterNames()
{
return _eventEmitters.Keys.ToArray();
}
public static void RemoveEventEmitter(string name)
{
if (_eventEmitters[name].isRemovable)
{
_eventEmitters.Remove(name);
}
}
public static void RegisterEngines(EnginesRoot enginesRoot)
{
_lastEngineRoot = enginesRoot;
// Register handlers before emitters so no events are missed
var entityFactory = enginesRoot.GenerateEntityFactory();
foreach (var key in _eventHandlers.Keys)
{
Logging.MetaDebugLog($"Registering IEventHandlerEngine {_eventHandlers[key].Name}");
enginesRoot.AddEngine(_eventHandlers[key]);
}
foreach (var key in _eventEmitters.Keys)
{
Logging.MetaDebugLog($"Registering IEventEmitterEngine {_eventEmitters[key].Name}");
_eventEmitters[key].Factory = entityFactory;
enginesRoot.AddEngine(_eventEmitters[key]);
}
}
}
}

View file

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// Built-in event types.
/// These are configured to fire when the API is initialized.
/// </summary>
public enum EventType
{
ApplicationInitialized,
Menu,
MenuSwitchedTo,
Game,
GameReloaded,
GameSwitchedTo
}
}

View file

@ -1,56 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Harmony;
using RobocraftX;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
using RobocraftX.CR.MainGame;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateGame()
/// </summary>
[HarmonyPatch]
class GameActivatedComposePatch
{
public static bool IsGameSwitching = false;
public static bool IsGameReloading = false;
public static void Postfix(ref object contextHolder, ref EnginesRoot enginesRoot)
{
// register custom game engines
GameEngineManager.RegisterEngines(enginesRoot);
// A new EnginesRoot is always created when ActivateGame is called
// so all event emitters and handlers must be re-registered.
EventManager.RegisterEngines(enginesRoot);
Logging.Log("Dispatching Game Activated event");
EventManager.GetEventEmitter("GamecraftModdingAPIGameActivatedEventEmitter").Emit();
if (IsGameSwitching)
{
IsGameSwitching = false;
Logging.Log("Dispatching Game Switched To event");
EventManager.GetEventEmitter("GamecraftModdingAPIGameSwitchedToEventEmitter").Emit();
}
if (IsGameReloading)
{
IsGameReloading = false;
Logging.Log("Dispatching Game Reloaded event");
EventManager.GetEventEmitter("GamecraftModdingAPIGameReloadedEventEmitter").Emit();
}
}
public static MethodBase TargetMethod()
{
return typeof(MainGameCompositionRoot).GetMethods().First(m => m.Name == "Compose")
.MakeGenericMethod(typeof(object));
}
}
}

View file

@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Harmony;
using RobocraftX;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// Patch of RobocraftX.FullGameCompositionRoot.ReloadGame()
/// </summary>
[HarmonyPatch(typeof(FullGameCompositionRoot), "ReloadGame")]
class GameReloadedPatch
{
public static void Postfix()
{
GameActivatedComposePatch.IsGameReloading = true;
}
}
}

View file

@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Harmony;
using RobocraftX;
using RobocraftX.CR.MainGame;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateGame()
/// (scheduled for execution during RobocraftX.FullGameCompositionRoot.SwitchToGame())
/// </summary>
[HarmonyPatch(typeof(FullGameCompositionRoot), "SwitchToGame")]
class GameSwitchedToPatch
{
public static void Prefix()
{
GameActivatedComposePatch.IsGameSwitching = true;
}
}
}

View file

@ -1,40 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// Engine interface to create a ModEventEntityStruct in entitiesDB when Emit() is called.
/// </summary>
public interface IEventEmitterEngine : IApiEngine
{
/// <summary>
/// The type of event emitted
/// </summary>
object type { get; }
/// <summary>
/// Whether the emitter can be removed with Manager.RemoveEventEmitter(name)
/// </summary>
bool isRemovable { get; }
/// <summary>
/// The EntityFactory for the entitiesDB.
/// Use this to create a ModEventEntityStruct when Emit() is called.
/// </summary>
IEntityFactory Factory { set; }
/// <summary>
/// Emit the event so IEventHandlerEngines can handle it.
/// Call Emit() to trigger the IEventEmitterEngine's event.
/// </summary>
void Emit();
}
}

View file

@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Harmony;
using RobocraftX;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateMenu()
/// </summary>
[HarmonyPatch(typeof(FullGameCompositionRoot), "ActivateMenu")]
class MenuActivatedPatch
{
private static bool firstLoad = true;
public static void Postfix(ref EnginesRoot ____frontEndEnginesRoot, FullGameCompositionRoot __instance)
{
// register custom menu engines
MenuEngineManager.RegisterEngines(____frontEndEnginesRoot);
// A new EnginesRoot is always created when ActivateMenu is called
// so all event emitters and handlers must be re-registered.
EventManager.RegisterEngines(____frontEndEnginesRoot);
if (firstLoad)
{
firstLoad = false;
FullGameFields.Init(__instance);
Logging.Log("Dispatching App Init event");
EventManager.GetEventEmitter("GamecraftModdingAPIApplicationInitializedEventEmitter").Emit();
}
Logging.Log("Dispatching Menu Activated event");
EventManager.GetEventEmitter("GamecraftModdingAPIMenuActivatedEventEmitter").Emit();
}
}
}

View file

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Harmony;
using RobocraftX;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// Patch of RobocraftX.FullGameCompositionRoot.SwitchToMenu()
/// </summary>
[HarmonyPatch(typeof(FullGameCompositionRoot), "SwitchToMenu")]
class MenuSwitchedToPatch
{
public static void Postfix()
{
// Event emitters and handlers should already be registered by MenuActivated event
Logging.Log("Dispatching Menu Switched To event");
EventManager.GetEventEmitter("GamecraftModdingAPIMenuSwitchedToEventEmitter").Emit();
}
}
}

View file

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// EntityDescriptor for creating ModEventEntityStructs
/// </summary>
public class ModEventEntityDescriptor : GenericEntityDescriptor<ModEventEntityStruct>
{
}
}

View file

@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// The event entity struct
/// </summary>
public struct ModEventEntityStruct : IEntityStruct
{
/// <summary>
/// The type of event that has been emitted
/// </summary>
public object type;
}
}

View file

@ -1,65 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// A simple implementation of IEventEmitterEngine sufficient for most uses
/// </summary>
public class SimpleEventEmitterEngine : IEventEmitterEngine
{
public string Name { get; set; }
public object type { get; set; }
public bool isRemovable { get; }
public IEntityFactory Factory { private get; set; }
public IEntitiesDB entitiesDB { set; private get; }
public void Ready() { }
/// <summary>
/// Emit the event
/// </summary>
public void Emit()
{
Factory.BuildEntity<ModEventEntityDescriptor>(ApiExclusiveGroups.eventID++, ApiExclusiveGroups.eventsExclusiveGroup)
.Init(new ModEventEntityStruct { type = type });
}
public void Dispose() { }
/// <summary>
/// Construct the engine
/// </summary>
/// <param name="type">The EventType to use for ModEventEntityStruct.type</param>
/// <param name="name">The name of this engine</param>
/// <param name="isRemovable">Will removing this engine not break your code?</param>
public SimpleEventEmitterEngine(EventType type, string name, bool isRemovable = true)
{
this.type = type;
this.Name = name;
this.isRemovable = isRemovable;
}
/// <summary>
/// Construct the engine
/// </summary>
/// <param name="type">The object to use for ModEventEntityStruct.type</param>
/// <param name="name">The name of this engine</param>
/// <param name="isRemovable">Will removing this engine not break your code?</param>
public SimpleEventEmitterEngine(object type, string name, bool isRemovable = true)
{
this.type = type;
this.Name = name;
this.isRemovable = isRemovable;
}
}
}

View file

@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace GamecraftModdingAPI.Events
{
/// <summary>
/// A simple implementation of IEventHandlerEngine sufficient for most uses
/// </summary>
public class SimpleEventHandlerEngine : IEventHandlerEngine
{
public object type { get; set; }
public string Name { get; set; }
private bool isActivated = false;
private readonly Action<IEntitiesDB> onActivated;
private readonly Action<IEntitiesDB> onDestroyed;
public IEntitiesDB entitiesDB { set; private get; }
public void Add(ref ModEventEntityStruct entityView, EGID egid)
{
if (entityView.type.Equals(this.type))
{
isActivated = true;
onActivated.Invoke(entitiesDB);
}
}
/// <summary>
/// Manually activate the EventHandler.
/// Once activated, the next remove event will not be ignored.
/// </summary>
/// <param name="handle">Whether to invoke the activated action</param>
public void Activate(bool handle = false)
{
isActivated = true;
}
public void Ready() { }
public void Remove(ref ModEventEntityStruct entityView, EGID egid)
{
if (entityView.type.Equals(this.type) && isActivated)
{
isActivated = false;
onDestroyed.Invoke(entitiesDB);
}
}
public void Dispose()
{
if (isActivated)
{
isActivated = false;
onDestroyed.Invoke(entitiesDB);
}
}
/// <summary>
/// Construct the engine
/// </summary>
/// <param name="activated">The operation to do when the event is created</param>
/// <param name="removed">The operation to do when the event is destroyed (if applicable)</param>
/// <param name="type">The type of event to handle</param>
/// <param name="name">The name of the engine</param>
/// <param name="simple">A useless parameter to use to avoid Python overload resolution errors</param>
public SimpleEventHandlerEngine(Action activated, Action removed, object type, string name, bool simple = true)
: this((IEntitiesDB _) => { activated.Invoke(); }, (IEntitiesDB _) => { removed.Invoke(); }, type, name) { }
/// <summary>
/// Construct the engine
/// </summary>
/// <param name="activated">The operation to do when the event is created</param>
/// <param name="removed">The operation to do when the event is destroyed (if applicable)</param>
/// <param name="type">The type of event to handler</param>
/// <param name="name">The name of the engine</param>
public SimpleEventHandlerEngine(Action<IEntitiesDB> activated, Action<IEntitiesDB> removed, object type, string name)
{
this.type = type;
this.Name = name;
this.onActivated = activated;
this.onDestroyed = removed;
}
}
}

View file

@ -1,549 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Version>0.1.3.0</Version>
<Authors>Exmods</Authors>
<PackageLicenseExpression>GNU General Public Licence 3+</PackageLicenseExpression>
<PackageProjectUrl>https://git.exmods.org/modtainers/GamecraftModdingAPI</PackageProjectUrl>
<NeutralLanguage>en-CA</NeutralLanguage>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Lib.Harmony" Version="1.2.0.1" />
</ItemGroup>
<!--Start Dependencies-->
<ItemGroup>
<Reference Include="Analytics">
<HintPath>..\ref\Gamecraft_Data\Managed\Analytics.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\ref\Gamecraft_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\ref\Gamecraft_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Authentication">
<HintPath>..\ref\Gamecraft_Data\Managed\Authentication.dll</HintPath>
</Reference>
<Reference Include="BlockEntityFactory">
<HintPath>..\ref\Gamecraft_Data\Managed\BlockEntityFactory.dll</HintPath>
</Reference>
<Reference Include="CommandLine">
<HintPath>..\ref\Gamecraft_Data\Managed\CommandLine.dll</HintPath>
</Reference>
<Reference Include="DataLoader">
<HintPath>..\ref\Gamecraft_Data\Managed\DataLoader.dll</HintPath>
</Reference>
<Reference Include="DDNA">
<HintPath>..\ref\Gamecraft_Data\Managed\DDNA.dll</HintPath>
</Reference>
<Reference Include="Facepunch.Steamworks.Win64">
<HintPath>..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath>
</Reference>
<Reference Include="FMOD">
<HintPath>..\ref\Gamecraft_Data\Managed\FMOD.dll</HintPath>
</Reference>
<Reference Include="FullGame">
<HintPath>..\ref\Gamecraft_Data\Managed\FullGame.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Blocks.ConsoleBlock">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Blocks.ConsoleBlock.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Effects">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Effects.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.GraphicsScreen">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.GraphicsScreen.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.Tweaks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.Tweaks.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.GUI.Wires">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.GUI.Wires.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Tweaks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Wires">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Wires.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Wires.Input">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Wires.Input.dll</HintPath>
</Reference>
<Reference Include="GameState">
<HintPath>..\ref\Gamecraft_Data\Managed\GameState.dll</HintPath>
</Reference>
<Reference Include="GPUInstancer">
<HintPath>..\ref\Gamecraft_Data\Managed\GPUInstancer.dll</HintPath>
</Reference>
<Reference Include="Havok.Physics">
<HintPath>..\ref\Gamecraft_Data\Managed\Havok.Physics.dll</HintPath>
</Reference>
<Reference Include="Havok.Physics.Hybrid">
<HintPath>..\ref\Gamecraft_Data\Managed\Havok.Physics.Hybrid.dll</HintPath>
</Reference>
<Reference Include="HdgRemoteDebugRuntime">
<HintPath>..\ref\Gamecraft_Data\Managed\HdgRemoteDebugRuntime.dll</HintPath>
</Reference>
<Reference Include="IllusionInjector">
<HintPath>..\ref\Gamecraft_Data\Managed\IllusionInjector.dll</HintPath>
</Reference>
<Reference Include="IllusionPlugin">
<HintPath>..\ref\Gamecraft_Data\Managed\IllusionPlugin.dll</HintPath>
</Reference>
<Reference Include="JWT">
<HintPath>..\ref\Gamecraft_Data\Managed\JWT.dll</HintPath>
</Reference>
<Reference Include="LZ4">
<HintPath>..\ref\Gamecraft_Data\Managed\LZ4.dll</HintPath>
</Reference>
<Reference Include="MultiplayerNetworking">
<HintPath>..\ref\Gamecraft_Data\Managed\MultiplayerNetworking.dll</HintPath>
</Reference>
<Reference Include="MultiplayerTest">
<HintPath>..\ref\Gamecraft_Data\Managed\MultiplayerTest.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\ref\Gamecraft_Data\Managed\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="RCX.ScreenshotTaker">
<HintPath>..\ref\Gamecraft_Data\Managed\RCX.ScreenshotTaker.dll</HintPath>
</Reference>
<Reference Include="Rewired_Core">
<HintPath>..\ref\Gamecraft_Data\Managed\Rewired_Core.dll</HintPath>
</Reference>
<Reference Include="Rewired_Windows">
<HintPath>..\ref\Gamecraft_Data\Managed\Rewired_Windows.dll</HintPath>
</Reference>
<Reference Include="Robocraft.MainGame.AutoEnterSimulation">
<HintPath>..\ref\Gamecraft_Data\Managed\Robocraft.MainGame.AutoEnterSimulation.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.AccountPreferences">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.AccountPreferences.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Blocks">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Blocks.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Blocks.Ghost">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Blocks.Ghost.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Blocks.Triggers">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Blocks.Triggers.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Character">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Character.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Common">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Common.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.ControlsScreen">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.ControlsScreen.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Crosshair">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Crosshair.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.EntityStreamUtility">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.EntityStreamUtility.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.FrontEnd">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.FrontEnd.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GameSignalHandling">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GameSignalHandling.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.DebugDisplay">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.DebugDisplay.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.RemoveBlock">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.RemoveBlock.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.ScaleGhost">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.ScaleGhost.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUI.SignalLabel">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUI.SignalLabel.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.GUIs.WorkshopPrefabs">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.GUIs.WorkshopPrefabs.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Input">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Input.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Inventory">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Inventory.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.MachineEditor">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.MachineEditor.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.MainGame">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.MainGame.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.MainSimulation">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.MainSimulation.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Multiplayer">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Multiplayer.NetworkEntityStream">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.NetworkEntityStream.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.MultiplayerInput">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.MultiplayerInput.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Party">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Party.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.PartyGui">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.PartyGui.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Physics">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Physics.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.PilotSeat">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.PilotSeat.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Player">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Player.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Priority">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Priority.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Rendering">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Rendering.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Rendering.Mock">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Rendering.Mock.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.SaveAndLoad">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.SaveAndLoad.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.SaveGameDialog">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.SaveGameDialog.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Serializers">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Serializers.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Services">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Services.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.SignalHandling">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.SignalHandling.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.StateSync">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.StateSync.dll</HintPath>
</Reference>
<Reference Include="RobocraftX_SpawnPoints">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX_SpawnPoints.dll</HintPath>
</Reference>
<Reference Include="RobocraftX_TextBlock">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX_TextBlock.dll</HintPath>
</Reference>
<Reference Include="RobocratX.SimulationCompositionRoot">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocratX.SimulationCompositionRoot.dll</HintPath>
</Reference>
<Reference Include="StringFormatter">
<HintPath>..\ref\Gamecraft_Data\Managed\StringFormatter.dll</HintPath>
</Reference>
<Reference Include="Svelto.Common">
<HintPath>..\ref\Gamecraft_Data\Managed\Svelto.Common.dll</HintPath>
</Reference>
<Reference Include="Svelto.ECS.Debugger">
<HintPath>..\ref\Gamecraft_Data\Managed\Svelto.ECS.Debugger.dll</HintPath>
</Reference>
<Reference Include="Svelto.ECS">
<HintPath>..\ref\Gamecraft_Data\Managed\Svelto.ECS.dll</HintPath>
</Reference>
<Reference Include="Svelto.Services">
<HintPath>..\ref\Gamecraft_Data\Managed\Svelto.Services.dll</HintPath>
</Reference>
<Reference Include="Svelto.Tasks">
<HintPath>..\ref\Gamecraft_Data\Managed\Svelto.Tasks.dll</HintPath>
</Reference>
<Reference Include="Unity.Addressables">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Addressables.dll</HintPath>
</Reference>
<Reference Include="Unity.Burst">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Burst.dll</HintPath>
</Reference>
<Reference Include="Unity.Burst.Unsafe">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Burst.Unsafe.dll</HintPath>
</Reference>
<Reference Include="Unity.Cloud.UserReporting.Client">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Cloud.UserReporting.Client.dll</HintPath>
</Reference>
<Reference Include="Unity.Cloud.UserReporting.Plugin">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Cloud.UserReporting.Plugin.dll</HintPath>
</Reference>
<Reference Include="Unity.Collections">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Collections.dll</HintPath>
</Reference>
<Reference Include="Unity.Entities">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.dll</HintPath>
</Reference>
<Reference Include="Unity.Entities.Hybrid">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.Hybrid.dll</HintPath>
</Reference>
<Reference Include="Unity.Entities.Properties">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.Properties.dll</HintPath>
</Reference>
<Reference Include="Unity.Entities.StaticTypeRegistry">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Entities.StaticTypeRegistry.dll</HintPath>
</Reference>
<Reference Include="Unity.Jobs">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Jobs.dll</HintPath>
</Reference>
<Reference Include="Unity.Mathematics">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Mathematics.dll</HintPath>
</Reference>
<Reference Include="Unity.Mathematics.Extensions">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Mathematics.Extensions.dll</HintPath>
</Reference>
<Reference Include="Unity.Mathematics.Extensions.Hybrid">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Mathematics.Extensions.Hybrid.dll</HintPath>
</Reference>
<Reference Include="Unity.Physics">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Physics.dll</HintPath>
</Reference>
<Reference Include="Unity.Physics.Hybrid">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Physics.Hybrid.dll</HintPath>
</Reference>
<Reference Include="Unity.Postprocessing.Runtime">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Postprocessing.Runtime.dll</HintPath>
</Reference>
<Reference Include="Unity.Properties">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Properties.dll</HintPath>
</Reference>
<Reference Include="Unity.RenderPipelines.Core.Runtime">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.RenderPipelines.Core.Runtime.dll</HintPath>
</Reference>
<Reference Include="Unity.RenderPipelines.Core.ShaderLibrary">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.RenderPipelines.Core.ShaderLibrary.dll</HintPath>
</Reference>
<Reference Include="Unity.RenderPipelines.Lightweight.Runtime">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.RenderPipelines.Lightweight.Runtime.dll</HintPath>
</Reference>
<Reference Include="Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll</HintPath>
</Reference>
<Reference Include="Unity.ResourceManager">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.ResourceManager.dll</HintPath>
</Reference>
<Reference Include="Unity.Scenes.Hybrid">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Scenes.Hybrid.dll</HintPath>
</Reference>
<Reference Include="Unity.ScriptableBuildPipeline">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.ScriptableBuildPipeline.dll</HintPath>
</Reference>
<Reference Include="Unity.TextMeshPro">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.TextMeshPro.dll</HintPath>
</Reference>
<Reference Include="Unity.Timeline">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Timeline.dll</HintPath>
</Reference>
<Reference Include="Unity.Transforms">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Transforms.dll</HintPath>
</Reference>
<Reference Include="Unity.Transforms.Hybrid">
<HintPath>..\ref\Gamecraft_Data\Managed\Unity.Transforms.Hybrid.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AccessibilityModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.AccessibilityModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AIModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.AIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AndroidJNIModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.AndroidJNIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.AnimationModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ARModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.ARModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AssetBundleModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.AssetBundleModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AudioModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.AudioModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ClothModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.ClothModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ClusterInputModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.ClusterInputModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ClusterRendererModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.ClusterRendererModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CrashReportingModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.CrashReportingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.DirectorModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.DirectorModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.DSPGraphModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.DSPGraphModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.FileSystemHttpModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.FileSystemHttpModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.GameCenterModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.GameCenterModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.GridModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.GridModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.HotReloadModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.HotReloadModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ImageConversionModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.ImageConversionModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.InputModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.InputModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.JSONSerializeModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.JSONSerializeModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.LocalizationModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.LocalizationModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ParticleSystemModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.ParticleSystemModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.PerformanceReportingModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.PerformanceReportingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.Physics2DModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.Physics2DModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ProfilerModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.ProfilerModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ScreenCaptureModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.ScreenCaptureModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.SharedInternalsModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.SharedInternalsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.SpriteMaskModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.SpriteMaskModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.SpriteShapeModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.SpriteShapeModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.StreamingModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.StreamingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.SubstanceModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.SubstanceModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TerrainModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.TerrainModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TerrainPhysicsModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.TerrainPhysicsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TextCoreModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.TextCoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TilemapModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.TilemapModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TLSModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.TLSModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UI.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIElementsModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UIElementsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UmbraModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UmbraModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UNETModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UNETModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityAnalyticsModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UnityAnalyticsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityConnectModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UnityConnectModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityTestProtocolModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UnityTestProtocolModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestAssetBundleModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UnityWebRequestAssetBundleModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestAudioModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UnityWebRequestAudioModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UnityWebRequestModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestTextureModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UnityWebRequestTextureModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestWWWModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.UnityWebRequestWWWModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VehiclesModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.VehiclesModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VFXModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.VFXModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VideoModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.VideoModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VRModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.VRModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.WindModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.WindModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.XRModule">
<HintPath>..\ref\Gamecraft_Data\Managed\UnityEngine.XRModule.dll</HintPath>
</Reference>
<Reference Include="uREPL">
<HintPath>..\ref\Gamecraft_Data\Managed\uREPL.dll</HintPath>
</Reference>
<Reference Include="UserReporting">
<HintPath>..\ref\Gamecraft_Data\Managed\UserReporting.dll</HintPath>
</Reference>
<Reference Include="VisualProfiler">
<HintPath>..\ref\Gamecraft_Data\Managed\VisualProfiler.dll</HintPath>
</Reference>
</ItemGroup>
<!--End Dependencies-->
</Project>

View file

@ -1,91 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Harmony;
using GamecraftModdingAPI.Utility;
using GamecraftModdingAPI.Events;
using GamecraftModdingAPI.Tasks;
namespace GamecraftModdingAPI
{
/// <summary>
/// The main class of the GamecraftModdingAPI.
/// Use this to initialize the API before calling it.
/// </summary>
public static class Main
{
private static HarmonyInstance harmony;
public static bool IsInitialized {
get { return harmony != null; }
}
private static int referenceCount = 0;
/// <summary>
/// Initializes the GamecraftModdingAPI.
/// Call this as soon as possible after Gamecraft starts up.
/// Ideally, this should be called from your main Plugin class's OnApplicationStart() method.
/// </summary>
public static void Init()
{
referenceCount++;
if (referenceCount > 1) { return; }
if (IsInitialized)
{
Logging.LogWarning("GamecraftModdingAPI.Main.Init() called but API is already initialized!");
return;
}
Logging.MetaDebugLog($"Patching Gamecraft");
var currentAssembly = Assembly.GetExecutingAssembly();
harmony = HarmonyInstance.Create(currentAssembly.GetName().Name);
harmony.PatchAll(currentAssembly);
// init utility
Logging.MetaDebugLog($"Initializing Utility");
Utility.GameState.Init();
// create default event emitters
Logging.MetaDebugLog($"Initializing Events");
EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.ApplicationInitialized, "GamecraftModdingAPIApplicationInitializedEventEmitter", false));
EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.Menu, "GamecraftModdingAPIMenuActivatedEventEmitter", false));
EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.MenuSwitchedTo, "GamecraftModdingAPIMenuSwitchedToEventEmitter", false));
EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.Game, "GamecraftModdingAPIGameActivatedEventEmitter", false));
EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.GameReloaded, "GamecraftModdingAPIGameReloadedEventEmitter", false));
EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.GameSwitchedTo, "GamecraftModdingAPIGameSwitchedToEventEmitter", false));
// init block implementors
Logging.MetaDebugLog($"Initializing Blocks");
Blocks.Movement.Init();
Blocks.Rotation.Init();
Blocks.Signals.Init();
Blocks.Placement.Init();
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized");
}
/// <summary>
/// Shuts down & cleans up the GamecraftModdingAPI.
/// Call this as late as possible before Gamecraft quits.
/// Ideally, this should be called from your main Plugin class's OnApplicationQuit() method.
/// </summary>
public static void Shutdown()
{
if (referenceCount > 0) { referenceCount--; }
if (referenceCount == 0)
{
if (!IsInitialized)
{
Logging.LogWarning("GamecraftModdingAPI.Main.Shutdown() called but API is not initialized!");
return;
}
Scheduler.Dispose();
var currentAssembly = Assembly.GetExecutingAssembly();
harmony.UnpatchAll(currentAssembly.GetName().Name);
harmony = null;
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} shutdown");
}
}
}
}

View file

@ -1,144 +0,0 @@
using System;
using System.Reflection;
using Harmony;
// test
using Svelto.ECS;
using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.SimulationModeState;
using GamecraftModdingAPI.Commands;
using GamecraftModdingAPI.Events;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Tests
{
// unused by design
/// <summary>
/// Modding API implemented as a standalone IPA Plugin.
/// Ideally, GamecraftModdingAPI should be loaded by another mod; not itself
/// </summary>
public class GamecraftModdingAPIPluginTest
#if DEBUG
: IllusionPlugin.IEnhancedPlugin
#endif
{
private static HarmonyInstance harmony { get; set; }
public string[] Filter { get; } = new string[] { "Gamecraft", "GamecraftPreview" };
public string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
public string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
public string HarmonyID { get; } = "org.git.exmods.modtainers.gamecraftmoddingapi";
public void OnApplicationQuit()
{
GamecraftModdingAPI.Main.Shutdown();
}
public void OnApplicationStart()
{
GamecraftModdingAPI.Main.Init();
// in case Steam is not installed/running
// this will crash the game slightly later during startup
//SteamInitPatch.ForcePassSteamCheck = true;
// in case running in a VM
//MinimumSpecsCheckPatch.ForcePassMinimumSpecCheck = true;
// disable some Gamecraft analytics
//AnalyticsDisablerPatch.DisableAnalytics = true;
// 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 :(
// debug/test handlers
EventManager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("App Inited event!"); }, () => { },
EventType.ApplicationInitialized, "appinit API debug"));
EventManager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("Menu Activated event!"); },
() => { Logging.Log("Menu Destroyed event!"); },
EventType.Menu, "menuact API debug"));
EventManager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("Menu Switched To event!"); }, () => { },
EventType.MenuSwitchedTo, "menuswitch API debug"));
EventManager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("Game Activated event!"); },
() => { Logging.Log("Game Destroyed event!"); },
EventType.Game, "gameact API debug"));
EventManager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("Game Reloaded event!"); }, () => { },
EventType.GameReloaded, "gamerel API debug"));
EventManager.AddEventHandler(new SimpleEventHandlerEngine(() => { Logging.Log("Game Switched To event!"); }, () => { },
EventType.GameSwitchedTo, "gameswitch API debug"));
// debug/test commands
if (Dependency.Hell("ExtraCommands"))
{
CommandManager.AddCommand(new SimpleCustomCommandEngine(() => { UnityEngine.Application.Quit(); },
"Exit", "Close Gamecraft without any prompts"));
CommandManager.AddCommand(new SimpleCustomCommandEngine<float>((float d) => { UnityEngine.Camera.main.fieldOfView = d; },
"SetFOV", "Set the player camera's field of view"));
CommandManager.AddCommand(new SimpleCustomCommandEngine<float, float, float>(
(x, y, z) => {
bool success = GamecraftModdingAPI.Blocks.Movement.MoveConnectedBlocks(
GamecraftModdingAPI.Blocks.BlockIdentifiers.LatestBlockID,
new Unity.Mathematics.float3(x, y, z));
if (!success)
{
GamecraftModdingAPI.Utility.Logging.CommandLogError("Blocks can only be moved in Build mode!");
}
}, "MoveLastBlock", "Move the most-recently-placed block, and any connected blocks by the given offset"));
CommandManager.AddCommand(new SimpleCustomCommandEngine<float, float, float>(
(x, y, z) => { Blocks.Placement.PlaceBlock(Blocks.BlockIDs.AluminiumCube, new Unity.Mathematics.float3(x, y, z)); },
"PlaceAluminium", "Place a block of aluminium at the given coordinates"));
Analytics.DeltaDNAHelper.PlayerLifetimeParameters plp = new Analytics.DeltaDNAHelper.PlayerLifetimeParameters();
CommandManager.AddCommand(new SimpleCustomCommandEngine<string>(
(s) => { Analytics.DeltaDNAHelper.SendActionCompletedEvent(in plp, s.Replace(", ", " ")); },
"SendAnalyticsAction", "Send an analytics action"));
System.Random random = new System.Random(); // for command below
CommandManager.AddCommand(new SimpleCustomCommandEngine(
() => {
if (!GameState.IsSimulationMode())
{
Logging.CommandLogError("You must be in simulation mode for this to work!");
return;
}
Tasks.Repeatable task = new Tasks.Repeatable(() => {
uint count = 0;
EGID[] eBlocks = Blocks.Signals.GetElectricBlocks();
for (uint i = 0u; i < eBlocks.Length; i++)
{
uint[] ids = Blocks.Signals.GetSignalIDs(eBlocks[i]);
for (uint j = 0u; j < ids.Length; j++)
{
Blocks.Signals.SetSignalByID(ids[j], (float)random.NextDouble());
count++;
}
}
Logging.MetaDebugLog($"Did the thing on {count} inputs");
},
() => { return GameState.IsSimulationMode(); });
Tasks.Scheduler.Schedule(task);
}, "RandomizeSignalsInputs", "Do the thing"));
}
// dependency test
if (Dependency.Hell("GamecraftScripting", new Version("0.0.1.0")))
{
Logging.LogWarning("You're in GamecraftScripting dependency hell");
}
else
{
Logging.Log("Compatible GamecraftScripting detected");
}
}
public void OnFixedUpdate() { }
public void OnLateUpdate() { }
public void OnLevelWasInitialized(int level) { }
public void OnLevelWasLoaded(int level) { }
public void OnUpdate() { }
}
}

View file

@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Analytics;
using Harmony;
using RobocraftX.Common;
using Svelto.ECS;
namespace GamecraftModdingAPI.Utility
{
/// <summary>
/// Patch of Analytics.AnalyticsCompositionRoot.Compose<T>(...)
/// This stops some analytics collection built into Gamecraft.
/// DO NOT USE! (This will likely crash your game on shutdown)
/// </summary>
[HarmonyPatch]
class AnalyticsDisablerPatch
{
/// <summary>
/// Don't activate gameplay analytics?
/// </summary>
public static bool DisableAnalytics = false;
public static bool Prefix(object contextHolder, EnginesRoot enginesRoot, RCXMode rcxMode)
{
return !DisableAnalytics;
}
public static MethodBase TargetMethod(HarmonyInstance instance)
{
return typeof(Analytics.AnalyticsCompositionRoot).GetMethod("Compose").MakeGenericMethod(typeof(object));
}
}
}

View file

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace GamecraftModdingAPI.Utility
{
public static class ApiExclusiveGroups
{
public static readonly ExclusiveGroup eventsExclusiveGroup = new ExclusiveGroup();
public static uint eventID;
}
}

View file

@ -1,79 +0,0 @@
using System;
using IllusionInjector;
using IllusionPlugin;
namespace GamecraftModdingAPI.Utility
{
/// <summary>
/// Simple plugin interaction operations
/// </summary>
public static class Dependency
{
/// <summary>
/// Find a plugin by name
/// </summary>
/// <returns>The plugin.</returns>
/// <param name="name">The plugin's name.</param>
public static IPlugin GetPlugin(string name)
{
foreach(IPlugin plugin in PluginManager.Plugins)
{
if (plugin.Name == name)
{
return plugin;
}
}
return null;
}
/// <summary>
/// Gets the plugin version.
/// This gives priority to the plugin's Version string but falls back to the Assembly's version
/// </summary>
/// <returns>The plugin's version.</returns>
/// <param name="name">The plugin's name.</param>
public static Version GetPluginVersion(string name)
{
IPlugin plugin = GetPlugin(name);
if (plugin != null) {
try
{
return new Version(plugin.Version);
} catch (Exception e) when (
e is ArgumentException
|| e is ArgumentNullException
|| e is ArgumentOutOfRangeException
|| e is FormatException
|| e is OverflowException) {}
return plugin.GetType().Assembly.GetName().Version;
}
return null;
}
// (I'm leaving the auto-generated version)
// <summary>
// Hell the specified name and version.
// </summary>
// <returns>The hell.</returns>
// <param name="name">Name.</param>
// <param name="version">Version.</param>
/// <summary>
/// Detect if you're in dependency hell with respect to the plugin.
/// ie Check if the plugin doesn't exist or is out of date.
/// When version is null, this only checks if the plugin exists.
/// The version is retrieved using GetPluginVersion(string name).
/// </summary>
/// <returns>Are you in dependency hell?</returns>
/// <param name="name">The plugin's name'</param>
/// <param name="version">The target version.</param>
public static bool Hell(string name, Version version = null)
{
Version pluginVersion = GetPluginVersion(name);
if (version == null) {
return pluginVersion == null;
}
return (pluginVersion == null || pluginVersion < version);
}
}
}

View file

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Harmony;
using RobocraftX.FrontEnd;
namespace GamecraftModdingAPI.Utility
{
/// <summary>
/// Patch of bool RobocraftX.FrontEnd.MinimumSpecsCheck.CheckRequirementsMet()
/// </summary>
[HarmonyPatch(typeof(MinimumSpecsCheck), "CheckRequirementsMet")]
class MinimumSpecsCheckPatch
{
/// <summary>
/// Ignore result of the requirement check?
/// </summary>
public static bool ForcePassMinimumSpecCheck = false;
public static void Postfix(ref bool __result)
{
__result = __result || ForcePassMinimumSpecCheck;
}
}
}

View file

@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Harmony;
using RobocraftX.Common;
namespace GamecraftModdingAPI.Utility
{
/// <summary>
/// Patch of bool RobocraftX.Common.SteamManager.VerifyOrInit()
/// This does not let you run Gamecraft without Steam.
/// DO NOT USE!
/// </summary>
[HarmonyPatch(typeof(SteamManager), "VerifyOrInit")]
class SteamInitPatch
{
/// <summary>
/// Ignore the result of steam initialization?
/// </summary>
public static bool ForcePassSteamCheck = false;
public static void Postfix(ref bool __result)
{
__result = __result || ForcePassSteamCheck;
}
}
}

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

@ -1,22 +1,31 @@
# GamecraftModdingAPI
# TechbloxModdingAPI
Unofficial Gamecraft modding API for interfacing Gamecraft from mods.
Unofficial Techblox modding API for interfacing Techblox from mods.
The GamecraftModdingAPI aims to simplify the mods in two ways:
- *Ease-of-Use* The API provides many easy methods to call to do common tasks such as moving blocks and adding commands.
All of the Harmony patchings are done for you, so you can focus on writing your mod instead of reading swathes of undocumented code.
The TechbloxModdingAPI aims to simplify the mods in two ways:
- *Ease-of-Use* The API provides convenient ways to do common tasks such as moving blocks and adding commands.
All of the Harmony patching is done for you, so you can focus on writing your mod instead of reading swathes of undocumented code.
- *Stability* The API aims to be reliable and consistent between versions.
This means your code won't break when the GamecraftModdingAPI or Gamecraft updates.
This means your code won't break when the TechbloxModdingAPI or Techblox updates.
For more info & support, please join the ExMods Discord: https://discord.gg/xjnFxQV
For more info, please check out the [official documentation](https://mod.exmods.org).
For more support, join the ExMods [Discord](https://discord.exmods.org).
## Installation
[Please follow the official mod installation guide](https://www.exmods.org/guides/install.html)
[Please follow the official mod installation guide](https://www.exmods.org/guides/install.html) or use GCMM.
## Development
To get started, create a symbolic link called `ref` in the root of the project, or one folder higher, linking to the Techblox install folder.
This will allow your IDE to resolve references to Techblox files for building and IDE tools.
TechbloxModdingAPI version numbers follow the [Semantic Versioning guidelines](https://semver.org/).
## External Libraries
GamecraftModdingAPI includes [Harmony](https://github.com/pardeike/Harmony) to modify the behaviour of existing Gamecraft code.
TechbloxModdingAPI includes [Harmony](https://github.com/pardeike/Harmony) to modify the behaviour of existing Techblox code.
# Disclaimer
This API is an unofficial modification of Gamecraft software, and is not endorsed or supported by FreeJam or Gamecraft.
The GamecraftModdingAPI developer(s) claim no rights on the Gamecraft code referenced within this project.
This API is an unofficial modification of Techblox software, and is not endorsed or supported by FreeJam or Techblox.
The TechbloxModdingAPI developer(s) claim no rights on the Techblox code referenced within this project.
All code contained in this project is licensed under the [GNU Public License v3](https://git.exmods.org/modtainers/TechbloxModdingAPI/src/branch/master/LICENSE).

44
TechbloxModdingAPI.sln Normal file
View file

@ -0,0 +1,44 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29411.108
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechbloxModdingAPI", "TechbloxModdingAPI\TechbloxModdingAPI.csproj", "{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGenerator", "CodeGenerator\CodeGenerator.csproj", "{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MakeEverythingPublicInGame", "MakeEverythingPublicInGame\MakeEverythingPublicInGame.csproj", "{391A3107-E5C6-4A04-9467-6D868AA9A8B4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Test|Any CPU = Test|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Release|Any CPU.ActiveCfg = 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.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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {72FB94D0-6C50-475B-81E0-C94C7D7A2A17}
EndGlobalSection
EndGlobal

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

@ -0,0 +1,35 @@
using System;
using TechbloxModdingAPI.Tests;
namespace TechbloxModdingAPI.App
{
#if TEST
/// <summary>
/// App callbacks tests.
/// Only available in TEST builds.
/// </summary>
[APITestClass]
public static class AppCallbacksTest
{
[APITestStartUp]
public static void StartUp()
{
// this could be split into 6 separate test cases
Game.Enter += Assert.CallsBack<GameEventArgs>("GameEnter");
Game.Exit += Assert.CallsBack<GameEventArgs>("GameExit");
Game.Simulate += Assert.CallsBack<GameEventArgs>("GameSimulate");
Game.Edit += Assert.CallsBack<GameEventArgs>("GameEdit");
Client.EnterMenu += Assert.CallsBack<MenuEventArgs>("MenuEnter");
Client.ExitMenu += Assert.CallsBack<MenuEventArgs>("MenuExit");
}
[APITestCase(TestType.Game)]
public static void Test()
{
// the test is actually completely implemented in StartUp()
// this is here just so it looks less weird (not required)
}
}
#endif
}

View file

@ -0,0 +1,42 @@
using System;
using System.Runtime.Serialization;
namespace TechbloxModdingAPI.App
{
public class AppException : TechbloxModdingAPIException
{
public AppException()
{
}
public AppException(string message) : base(message)
{
}
public AppException(string message, Exception innerException) : base(message, innerException)
{
}
}
public class AppStateException : AppException
{
public AppStateException()
{
}
public AppStateException(string message) : base(message)
{
}
}
public class GameNotFoundException : AppException
{
public GameNotFoundException()
{
}
public GameNotFoundException(string message) : base(message)
{
}
}
}

View file

@ -0,0 +1,171 @@
using System;
using System.Reflection;
using HarmonyLib;
using RobocraftX.Services;
using UnityEngine;
using RobocraftX.Common;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App
{
/// <summary>
/// The Techblox application that is running this code right now.
/// </summary>
public class Client
{
public static Client Instance { get; } = new Client();
protected static Func<object> ErrorHandlerInstanceGetter;
protected static Action<object, Error> EnqueueError;
/// <summary>
/// An event that fires whenever the main menu is loaded.
/// </summary>
public static event EventHandler<MenuEventArgs> EnterMenu
{
add => Game.menuEngine.EnterMenu += value;
remove => Game.menuEngine.EnterMenu -= value;
}
/// <summary>
/// An event that fire whenever the main menu is exited.
/// </summary>
public static event EventHandler<MenuEventArgs> ExitMenu
{
add => Game.menuEngine.ExitMenu += value;
remove => Game.menuEngine.ExitMenu -= value;
}
/// <summary>
/// Techblox build version string.
/// Usually this is in the form YYYY.mm.DD.HH.MM.SS
/// </summary>
/// <value>The version.</value>
public string Version
{
get => Application.version;
}
/// <summary>
/// Unity version string.
/// </summary>
/// <value>The unity version.</value>
public string UnityVersion
{
get => Application.unityVersion;
}
/// <summary>
/// Game saves currently visible in the menu.
/// These take a second to completely populate after the EnterMenu event fires.
/// </summary>
/// <value>My games.</value>
public Game[] MyGames
{
get
{
if (!Game.menuEngine.IsInMenu) return Array.Empty<Game>();
return Game.menuEngine.GetMyGames();
}
}
/// <summary>
/// Whether Techblox is in the Main Menu
/// </summary>
/// <value><c>true</c> if in menu; <c>false</c> when loading or in a game.</value>
public bool InMenu
{
get => Game.menuEngine.IsInMenu;
}
/// <summary>
/// Open a popup which prompts the user to click a button.
/// This reuses Techblox's error dialog popup
/// </summary>
/// <param name="popup">The popup to display. Use an instance of SingleChoicePrompt or DualChoicePrompt.</param>
public void PromptUser(Error popup)
{
// if the stuff wasn't mostly set to internal, this would be written as:
// RobocraftX.Services.ErrorHandler.Instance.EqueueError(error);
object errorHandlerInstance = ErrorHandlerInstanceGetter();
EnqueueError(errorHandlerInstance, popup);
}
public void CloseCurrentPrompt()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
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()
{
// this would have been so much simpler if this didn't involve a bunch of internal fields & classes
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
Type errorHandle = AccessTools.TypeByName("RobocraftX.Services.ErrorHandle");
ErrorHandlerInstanceGetter = (Func<object>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenInstanceGetter")
.MakeGenericMethod(errorHandler)
.Invoke(null, new object[0]);
EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError")
.MakeGenericMethod(errorHandler, errorHandle)
.Invoke(null, new object[0]);
}
// Creating delegates once is faster than reflection every time
// Admittedly, this way is more difficult to code and less readable
private static Func<object> GenInstanceGetter<T>()
{
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
MethodInfo instance = AccessTools.PropertyGetter(errorHandler, "Instance");
Func<T> getterSimple = (Func<T>) Delegate.CreateDelegate(typeof(Func<T>), null, instance);
Func<object> getterCasted = () => (object) getterSimple();
return getterCasted;
}
private static Action<object, Error> GenEnqueueError<T, TRes>()
{
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
MethodInfo enqueueError = AccessTools.Method(errorHandler, "EnqueueError");
Func<T, Error, TRes> enqueueSimple =
(Func<T, Error, TRes>) Delegate.CreateDelegate(typeof(Func<T, Error, TRes>), enqueueError);
Action<object, Error> enqueueCasted =
(object instance, Error error) => { enqueueSimple((T) instance, error); };
return enqueueCasted;
}
private static (Action Close, Action FirstButton, Action SecondButton) _errorPopup;
private static (Action Close, Action FirstButton, Action SecondButton) GetPopupCloseMethods(object handler)
{
if (_errorPopup.Close != null)
return _errorPopup;
Type errorHandler = handler.GetType();
FieldInfo field = AccessTools.Field(errorHandler, "errorPopup");
var errorPopup = (ErrorPopup)field.GetValue(handler);
MethodInfo info = AccessTools.Method(errorPopup.GetType(), "ClosePopup");
var close = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleFirstOption");
var first = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleSecondOption");
var second = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
_errorPopup = (close, first, second);
return _errorPopup;
}
}
}

View file

@ -0,0 +1,67 @@
using System;
using HarmonyLib;
using RobocraftX.Services;
using TechbloxModdingAPI.Tests;
namespace TechbloxModdingAPI.App
{
#if TEST
/// <summary>
/// Client popups tests.
/// Only available in TEST builds.
/// </summary>
[APITestClass]
public static class ClientAlertTest
{
private static DualChoicePrompt popup2 = null;
private static SingleChoicePrompt popup1 = null;
[APITestStartUp]
public static void StartUp2()
{
popup2 = new DualChoicePrompt("This is a test double-button popup",
"The cake is a lie",
"lmao",
() => { },
"kek",
() => { });
}
[APITestStartUp]
public static void StartUp1()
{
popup1 = new SingleChoicePrompt("The cake is a lie",
"This is a test single-button popup",
"qwertyuiop",
() => { });
}
[APITestCase(TestType.Menu)]
public static void TestPopUp2()
{
Client.Instance.PromptUser(popup2);
}
[APITestCase(TestType.Menu)]
public static void TestPopUp1()
{
Client.Instance.PromptUser(popup1);
}
[APITestCase(TestType.Menu)]
public static void TestPopUpClose1()
{
Client.Instance.CloseCurrentPrompt();
}
[APITestCase(TestType.Menu)]
public static void TestPopUpClose2()
{
Client.Instance.CloseCurrentPrompt();
}
}
#endif
}

View file

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

View file

@ -0,0 +1,490 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using RobocraftX.GUI.MyGamesScreen;
using Svelto.ECS;
using Techblox.GameSelection;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App
{
/// <summary>
/// An in-game save.
/// This can be a menu item for a local save or the currently loaded save.
/// Support for Steam Workshop coming soon (hopefully).
/// </summary>
public class Game
{
// extensible engines
protected static GameGameEngine gameEngine = new GameGameEngine();
protected internal static GameMenuEngine menuEngine = new GameMenuEngine();
protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine();
protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine();
private List<string> debugIds = new List<string>();
private bool menuMode = true;
private bool hasId = false;
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.App.Game"/> class.
/// </summary>
/// <param name="id">Menu identifier.</param>
public Game(uint id) : this(new EGID(id, MyGamesScreenExclusiveGroups.MyGames))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.App.Game"/> class.
/// </summary>
/// <param name="id">Menu identifier.</param>
public Game(EGID id)
{
this.Id = id.entityID;
this.EGID = id;
this.hasId = true;
menuMode = true;
if (!VerifyMode()) throw new AppStateException("Game cannot be created while not in a game nor in a menu (is the game in a loading screen?)");
}
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.App.Game"/> class without id.
/// This is assumed to be the current game.
/// </summary>
public Game()
{
menuMode = false;
if (!VerifyMode()) throw new AppStateException("Game cannot be created while not in a game nor in a menu (is the game in a loading screen?)");
if (menuEngine.IsInMenu) throw new GameNotFoundException("Game not found.");
}
/// <summary>
/// Returns the currently loaded game.
/// If in a menu, manipulating the returned object may not work as intended.
/// </summary>
/// <returns>The current game.</returns>
public static Game CurrentGame()
{
return new Game();
}
/// <summary>
/// Creates a new game and adds it to the menu.
/// If not in a menu, this will throw AppStateException.
/// </summary>
/// <returns>The new game.</returns>
public static Game NewGame()
{
if (!menuEngine.IsInMenu) throw new AppStateException("New Game cannot be created while not in a menu.");
uint nextId = menuEngine.HighestID() + 1;
EGID egid = new EGID(nextId, MyGamesScreenExclusiveGroups.MyGames);
menuEngine.CreateMyGame(egid);
return new Game(egid);
}
/// <summary>
/// An event that fires whenever a game is switched to simulation mode (time running mode).
/// </summary>
public static event EventHandler<GameEventArgs> Simulate
{
add => buildSimEventEngine.SimulationMode += value;
remove => buildSimEventEngine.SimulationMode -= value;
}
/// <summary>
/// An event that fires whenever a game is switched to edit mode (time stopped mode).
/// This does not fire when a game is loaded.
/// </summary>
public static event EventHandler<GameEventArgs> Edit
{
add => buildSimEventEngine.BuildMode += value;
remove => buildSimEventEngine.BuildMode -= value;
}
/// <summary>
/// An event that fires right after a game is completely loaded.
/// </summary>
public static event EventHandler<GameEventArgs> Enter
{
add => gameEngine.EnterGame += value;
remove => gameEngine.EnterGame -= value;
}
/// <summary>
/// An event that fires right before a game returns to the main menu.
/// At this point, Techblox is transitioning state so many things are invalid/unstable here.
/// </summary>
public static event EventHandler<GameEventArgs> Exit
{
add => gameEngine.ExitGame += value;
remove => gameEngine.ExitGame -= value;
}
/// <summary>
/// The game's unique menu identifier.
/// </summary>
/// <value>The identifier.</value>
public uint Id
{
get;
private set;
}
/// <summary>
/// The game's unique menu EGID.
/// </summary>
/// <value>The egid.</value>
public EGID EGID
{
get;
private set;
}
/// <summary>
/// Whether the game is a (valid) menu item.
/// </summary>
/// <value><c>true</c> if menu item; otherwise, <c>false</c>.</value>
public bool MenuItem
{
get => menuMode && hasId;
}
/// <summary>
/// The game's name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get
{
if (!VerifyMode()) return null;
if (menuMode) return menuEngine.GetGameInfo(EGID).GameName;
return gameEngine.GetGameData().saveName;
}
set
{
if (!VerifyMode()) return;
if (menuMode)
{
menuEngine.SetGameName(EGID, value);
} // Save details are directly saved from user input or not changed at all when in game
}
}
/// <summary>
/// The game's description.
/// </summary>
/// <value>The description.</value>
public string Description
{
get
{
if (!VerifyMode()) return null;
if (menuMode) return menuEngine.GetGameInfo(EGID).GameDescription;
return "";
}
set
{
if (!VerifyMode()) return;
if (menuMode)
{
menuEngine.SetGameDescription(EGID, value);
} // No description exists in-game
}
}
/// <summary>
/// The path to the game's save folder.
/// </summary>
/// <value>The path.</value>
public string Path
{
get
{
if (!VerifyMode()) return null;
if (menuMode) return menuEngine.GetGameInfo(EGID).SavedGamePath;
return gameEngine.GetGameData().gameID;
}
set
{
if (!VerifyMode()) return;
if (menuMode)
{
menuEngine.GetGameInfo(EGID).SavedGamePath.Set(value);
}
}
}
/// <summary>
/// The Steam Workshop Id of the game save.
/// In most cases this is invalid and returns 0, so this can be ignored.
/// </summary>
/// <value>The workshop identifier.</value>
[Obsolete]
public ulong WorkshopId
{
get
{
return 0uL; // Not supported anymore
}
set
{
}
}
/// <summary>
/// Whether the game is in simulation mode.
/// </summary>
/// <value><c>true</c> if is simulating; otherwise, <c>false</c>.</value>
public bool IsSimulating
{
get
{
if (!VerifyMode()) return false;
return !menuMode && gameEngine.IsTimeRunningMode();
}
set
{
if (!VerifyMode()) return;
if (!menuMode && gameEngine.IsTimeRunningMode() != value)
gameEngine.ToggleTimeMode();
}
}
/// <summary>
/// Whether the game is in time-running mode.
/// Alias of IsSimulating.
/// </summary>
/// <value><c>true</c> if is time running; otherwise, <c>false</c>.</value>
public bool IsTimeRunning
{
get => IsSimulating;
set
{
IsSimulating = value;
}
}
/// <summary>
/// Whether the game is in time-stopped mode.
/// </summary>
/// <value><c>true</c> if is time stopped; otherwise, <c>false</c>.</value>
public bool IsTimeStopped
{
get
{
if (!VerifyMode()) return false;
return !menuMode && gameEngine.IsTimeStoppedMode();
}
set
{
if (!VerifyMode()) return;
if (!menuMode && gameEngine.IsTimeStoppedMode() != value)
gameEngine.ToggleTimeMode();
}
}
/// <summary>
/// Toggles the time mode.
/// </summary>
public void ToggleTimeMode()
{
if (!VerifyMode()) return;
if (menuMode || !gameEngine.IsInGame)
{
throw new AppStateException("Game menu item cannot toggle it's time mode");
}
gameEngine.ToggleTimeMode();
}
/// <summary>
/// The mode of the game.
/// </summary>
public CurrentGameMode Mode
{
get
{
if (menuMode || !VerifyMode()) return CurrentGameMode.None;
return gameEngine.GetGameData().gameMode == GameMode.CreateWorld ? CurrentGameMode.Build : CurrentGameMode.Play;
}
}
/// <summary>
/// Load the game save.
/// This happens asynchronously, so when this method returns the game not loaded yet.
/// Use the Game.Enter event to perform operations after the game has completely loaded.
/// </summary>
public void EnterGame()
{
if (!VerifyMode()) return;
if (!hasId)
{
throw new GameNotFoundException("Game has an invalid ID");
}
ISchedulable task = new Once(() => { menuEngine.EnterGame(EGID); this.menuMode = false; });
Scheduler.Schedule(task);
}
/// <summary>
/// Return to the menu.
/// Part of this always happens asynchronously, so when this method returns the game has not exited yet.
/// Use the Client.EnterMenu event to perform operations after the game has completely exited.
/// </summary>
/// <param name="async">If set to <c>true</c>, do this async.</param>
public void ExitGame(bool async = false)
{
if (!VerifyMode()) return;
if (menuMode)
{
throw new GameNotFoundException("Cannot exit game using menu ID");
}
gameEngine.ExitCurrentGame(async);
this.menuMode = true;
}
/// <summary>
/// Saves the game.
/// Part of this happens asynchronously, so when this method returns the game has not been saved yet.
/// </summary>
public void SaveGame()
{
if (!VerifyMode()) return;
if (menuMode)
{
throw new GameNotFoundException("Cannot save game using menu ID");
}
gameEngine.SaveCurrentGame();
}
/// <summary>
/// Add information to the in-game debug display.
/// When this object is garbage collected, this debug info is automatically removed.
/// The provided getter function is called each frame so make sure it returns quickly.
/// </summary>
/// <param name="id">Debug info identifier.</param>
/// <param name="contentGetter">A function that returns the current information.</param>
public void AddDebugInfo(string id, Func<string> contentGetter)
{
if (!VerifyMode()) return;
if (menuMode)
{
throw new GameNotFoundException("Game object references a menu item but AddDebugInfo only works on the currently-loaded game");
}
debugOverlayEngine.SetInfo("game_" + id, contentGetter);
debugIds.Add(id);
}
/// <summary>
/// Remove information from the in-game debug display.
/// </summary>
/// <returns><c>true</c>, if debug info was removed, <c>false</c> otherwise.</returns>
/// <param name="id">Debug info identifier.</param>
public bool RemoveDebugInfo(string id)
{
if (!VerifyMode()) return false;
if (menuMode)
{
throw new GameNotFoundException("Game object references a menu item but RemoveDebugInfo only works on the currently-loaded game");
}
if (!debugIds.Contains(id)) return false;
debugOverlayEngine.RemoveInfo("game_" + id);
return debugIds.Remove(id);
}
/// <summary>
/// Add information to the in-game debug display.
/// This debug info will be present for all games until it is manually removed.
/// The provided getter function is called each frame so make sure it returns quickly.
/// </summary>
/// <param name="id">Debug info identifier.</param>
/// <param name="contentGetter">A function that returns the current information.</param>
public static void AddPersistentDebugInfo(string id, Func<string> contentGetter)
{
debugOverlayEngine.SetInfo("persistent_" + id, contentGetter);
}
/// <summary>
/// Remove persistent information from the in-game debug display.
/// </summary>
/// <returns><c>true</c>, if debug info was removed, <c>false</c> otherwise.</returns>
/// <param name="id">Debug info identifier.</param>
public static bool RemovePersistentDebugInfo(string id)
{
return debugOverlayEngine.RemoveInfo("persistent_" + id);
}
/// <summary>
/// Gets the blocks in the game.
/// This returns null when in a loading state, and throws AppStateException when in menu.
/// </summary>
/// <returns>The blocks in game.</returns>
/// <param name="filter">The block to search for. BlockIDs.Invalid will return all blocks.</param>
public Block[] GetBlocksInGame(BlockIDs filter = BlockIDs.Invalid)
{
if (!VerifyMode()) return null;
if (menuMode)
{
throw new AppStateException("Game object references a menu item but GetBlocksInGame only works on the currently-loaded game");
}
EGID[] blockEGIDs = gameEngine.GetAllBlocksInGame(filter);
Block[] blocks = new Block[blockEGIDs.Length];
for (int b = 0; b < blockEGIDs.Length; b++)
{
blocks[b] = Block.New(blockEGIDs[b]);
}
return blocks;
}
/// <summary>
/// Enable the screenshot taker for updating the game's screenshot. Breaks the pause menu in a new save.
/// </summary>
public void EnableScreenshotTaker()
{
if (!VerifyMode()) return;
gameEngine.EnableScreenshotTaker();
}
~Game()
{
foreach (string id in debugIds)
{
debugOverlayEngine.RemoveInfo(id);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool VerifyMode()
{
if (menuMode && (!menuEngine.IsInMenu || gameEngine.IsInGame))
{
// either game loading or API is broken
return false;
}
if (!menuMode && (menuEngine.IsInMenu || !gameEngine.IsInGame))
{
// either game loading or API is broken
return false;
}
return true;
}
internal static void Init()
{
GameEngineManager.AddGameEngine(gameEngine);
GameEngineManager.AddGameEngine(debugOverlayEngine);
GameEngineManager.AddGameEngine(buildSimEventEngine);
MenuEngineManager.AddMenuEngine(menuEngine);
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using RobocraftX.Common;
using RobocraftX.StateSync;
using Svelto.ECS;
using Unity.Jobs;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App
{
public class GameBuildSimEventEngine : IApiEngine, IUnorderedInitializeOnTimeRunningModeEntered, IUnorderedInitializeOnTimeStoppedModeEntered
{
public WrappedHandler<GameEventArgs> SimulationMode;
public WrappedHandler<GameEventArgs> BuildMode;
public string Name => "TechbloxModdingAPIBuildSimEventGameEngine";
public bool isRemovable => false;
public EntitiesDB entitiesDB { set; private get; }
public void Dispose() { }
public void Ready() { }
public JobHandle OnInitializeTimeRunningMode(JobHandle inputDeps)
{
SimulationMode.Invoke(this, new GameEventArgs { GameName = "", GamePath = "" }); // TODO
return inputDeps;
}
public JobHandle OnInitializeTimeStoppedMode(JobHandle inputDeps)
{
BuildMode.Invoke(this, new GameEventArgs { GameName = "", GamePath = "" });
return inputDeps;
}
}
public struct GameEventArgs
{
public string GameName;
public string GamePath;
}
}

View file

@ -0,0 +1,195 @@
using System.Collections.Generic;
using HarmonyLib;
using RobocraftX;
using RobocraftX.Common;
using RobocraftX.Schedulers;
using RobocraftX.SimulationModeState;
using Svelto.ECS;
using Svelto.Tasks;
using Svelto.Tasks.Lean;
using RobocraftX.Blocks;
using RobocraftX.Common.Loading;
using RobocraftX.Multiplayer;
using RobocraftX.ScreenshotTaker;
using Techblox.Environment.Transition;
using Techblox.GameSelection;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App
{
public class GameGameEngine : IApiEngine, IReactOnAddAndRemove<LoadingActionEntityStruct>
{
public WrappedHandler<GameEventArgs> EnterGame;
public WrappedHandler<GameEventArgs> ExitGame;
public string Name => "TechbloxModdingAPIGameInfoMenuEngine";
public bool isRemovable => false;
public EntitiesDB entitiesDB { set; private get; }
private bool enteredGame;
private bool loadingFinished;
private bool playerJoined;
public void Dispose()
{
if (GameReloadedPatch.IsReload)
return; // Toggling time mode
ExitGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID });
IsInGame = false;
loadingFinished = false;
playerJoined = false;
enteredGame = false;
}
public void Ready()
{
if (GameReloadedPatch.IsReload)
return; // Toggling time mode
enteredGame = true;
Player.Joined += OnPlayerJoined;
}
private void OnPlayerJoined(object sender, PlayerEventArgs args)
{
if (args.Player.Type != PlayerType.Local) return;
playerJoined = true;
Player.Joined -= OnPlayerJoined;
CheckJoinEvent();
}
// game functionality
public bool IsInGame
{
get;
private set;
} = false;
public void ExitCurrentGame(bool async = false)
{
if (async)
{
ExitCurrentGameAsync().RunOn(ClientLean.EveryFrameStepRunner_TimeRunningAndStopped);
}
else
{
entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID).WantsToQuit = true;
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID);
}
}
public IEnumerator<TaskContract> ExitCurrentGameAsync()
{
/*
while (Lean.EveryFrameStepRunner_RUNS_IN_TIME_STOPPED_AND_RUNNING.isStopping) { yield return Yield.It; }
AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToMenu").Invoke(FullGameFields.Instance, new object[0]);*/
yield return Yield.It;
entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID).WantsToQuit = true;
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID);
}
public void SaveCurrentGame()
{
ref GameSceneEntityStruct gses = ref entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID);
gses.LoadAfterSaving = false;
gses.SaveNow = true;
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID);
}
public bool IsTimeRunningMode()
{
return TimeRunningModeUtil.IsTimeRunningMode(entitiesDB);
}
public bool IsTimeStoppedMode()
{
return TimeRunningModeUtil.IsTimeStoppedMode(entitiesDB);
}
public void ToggleTimeMode()
{
if (TimeRunningModeUtil.IsTimeStoppedMode(entitiesDB))
FakeInput.ActionInput(toggleMode: true);
else
{
IEnumerator<TaskContract> ReloadBuildModeTask()
{
SwitchAnimationUtil.Start(entitiesDB);
while (SwitchAnimationUtil.IsFadeOutActive(entitiesDB))
yield return (TaskContract)Yield.It;
FullGameFields._multiplayerParams.MultiplayerMode = MultiplayerMode.SinglePlayer;
AccessTools.Method(typeof(FullGameCompositionRoot), "ReloadGame")
.Invoke(FullGameFields.Instance, new object[] { });
}
ReloadBuildModeTask().RunOn(ClientLean.UIScheduler);
}
}
public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid)
{
var allBlocks = entitiesDB.QueryEntities<BlockTagEntityStruct>();
List<EGID> blockEGIDs = new List<EGID>();
foreach (var ((_, 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)
dbid = (uint)filter;
else
dbid = entitiesDB.QueryEntity<DBEntityStruct>(id).DBID;
var ownership = entitiesDB.QueryEntity<BlockOwnershipComponent>(id).BlockOwnership;
if ((ownership & BlockOwnership.User) != 0 && dbid == (ulong)filter)
blockEGIDs.Add(id);
}
}
return blockEGIDs.ToArray();
}
public void EnableScreenshotTaker()
{
ref var local = ref entitiesDB.QueryEntity<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker);
if (local.enabled)
return;
local.enabled = true;
entitiesDB.PublishEntityChange<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker);
}
public GameSelectionComponent GetGameData()
{
return entitiesDB.QueryEntity<GameSelectionComponent>(GameSelectionConstants.GameSelectionEGID);
}
public void Add(ref LoadingActionEntityStruct entityComponent, EGID egid)
{
}
public void Remove(ref LoadingActionEntityStruct entityComponent, EGID egid)
{ // Finished loading
if (!enteredGame) return;
enteredGame = false;
loadingFinished = true;
CheckJoinEvent();
}
private void CheckJoinEvent()
{
if (!loadingFinished || !playerJoined) return;
EnterGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID });
IsInGame = true;
}
}
}

View file

@ -0,0 +1,197 @@
using System;
using System.Reflection;
using HarmonyLib;
using RobocraftX;
using RobocraftX.GUI;
using RobocraftX.GUI.MyGamesScreen;
using RobocraftX.Multiplayer;
using Svelto.ECS;
using Svelto.ECS.Experimental;
using Techblox.GameSelection;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App
{
public class GameMenuEngine : IFactoryEngine
{
public WrappedHandler<MenuEventArgs> EnterMenu;
public WrappedHandler<MenuEventArgs> ExitMenu;
public IEntityFactory Factory { set; private get; }
public string Name => "TechbloxModdingAPIGameInfoGameEngine";
public bool isRemovable => false;
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 Ready()
{
MenuEnteredEnginePatch.IsInMenu = true; // At first it uses ActivateMenu(), then GoToMenu() which is patched
MenuEnteredEnginePatch.EnteredExitedMenu();
}
// game functionality
public bool IsInMenu => MenuEnteredEnginePatch.IsInMenu;
public Game[] GetMyGames()
{
var (mgsevs, count) = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
Game[] games = new Game[count];
for (int i = 0; i < count; i++)
{
Utility.Logging.MetaDebugLog($"Found game named {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)
{
EntityInitializer eci = Factory.BuildEntity<MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal>(id);
eci.Init(new MyGameDataEntityStruct
{
SavedGamePath = new ECSString(path),
ThumbnailId = thumbnailId,
GameName = new ECSString(gameName),
CreatorName = new ECSString(creatorName),
GameDescription = new ECSString(description),
CreatedDate = createdDate,
});
// entitiesDB.PublishEntityChange<MyGameDataEntityStruct>(id); // this will always fail
return true;
}
public uint HighestID()
{
var (games, count) = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
uint max = 0;
for (int i = 0; i < count; i++)
{
if (games[i].ID.entityID > max)
{
max = games[i].ID.entityID;
}
}
return max;
}
public bool EnterGame(EGID id)
{
if (!ExistsGameInfo(id)) return false;
ref MyGameDataEntityStruct mgdes = ref GetGameInfo(id);
return EnterGame(mgdes.GameName, mgdes.FileId);
}
public bool EnterGame(ECSString gameName, string fileId, bool autoEnterSim = false)
{
FullGameFields._multiplayerParams.MultiplayerMode = MultiplayerMode.SinglePlayer;
ref var selection = ref entitiesDB.QueryEntity<GameSelectionComponent>(GameSelectionConstants.GameSelectionEGID);
selection.userContentID.Set(fileId);
selection.triggerStart = true;
selection.saveType = SaveType.ExistingSave;
selection.saveName = gameName;
selection.gameMode = GameMode.PlayGame;
selection.gameID.Set("GAMEID_Road_Track"); //TODO: Expose to the API
return true;
}
public bool SetGameName(EGID id, string name)
{
if (!ExistsGameInfo(id)) return false;
GetGameInfo(id).GameName.Set(name);
GetGameViewInfo(id).MyGamesSlotComponent.GameName = StringUtil.SanitiseString(name);
return true;
}
public bool SetGameDescription(EGID id, string name)
{
if (!ExistsGameInfo(id)) return false;
GetGameInfo(id).GameDescription.Set(name);
GetGameViewInfo(id).MyGamesSlotComponent.GameDescription = StringUtil.SanitiseString(name);
return true;
}
public bool ExistsGameInfo(EGID id)
{
return entitiesDB.Exists<MyGameDataEntityStruct>(id);
}
public ref MyGameDataEntityStruct GetGameInfo(EGID id)
{
return ref GetComponent<MyGameDataEntityStruct>(id);
}
public dynamic GetGameViewInfo(EGID id)
{
dynamic structOptional = AccessTools.Method("TechbloxModdingAPI.Utility.NativeApiExtensions:QueryEntityOptional", new []{typeof(EntitiesDB), typeof(EGID)})
.MakeGenericMethod(AccessTools.TypeByName("RobocraftX.GUI.MyGamesScreen.MyGamesSlotEntityViewStruct"))
.Invoke(null, new object[] {entitiesDB, new EGID(id.entityID, MyGamesScreenExclusiveGroups.GameSlotGuiEntities)});
if (structOptional == null) throw new Exception("Could not get game slot entity");
return structOptional ? structOptional : null;
}
public ref T GetComponent<T>(EGID id) where T: unmanaged, IEntityComponent
{
return ref entitiesDB.QueryEntity<T>(id);
}
}
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

@ -0,0 +1,27 @@
using System;
using HarmonyLib;
using RobocraftX.Services;
namespace TechbloxModdingAPI.App
{
public class DualChoicePrompt : MultiChoiceError
{
public DualChoicePrompt(string errorMessage, string title, string firstButtonText, Action firstButtonAction, string secondButtonText, Action secondButtonAction) : base(errorMessage, firstButtonText, firstButtonAction, secondButtonText, secondButtonAction)
{
// internal readonly field smh
new Traverse(this).Field<string>("Title").Value = title;
}
}
public class SingleChoicePrompt : SingleChoiceError
{
public SingleChoicePrompt(string errorMessage, string buttonText, Action buttonClickAction) : base(errorMessage, buttonText, buttonClickAction)
{
}
public SingleChoicePrompt(string titleText, string errorMessage, string buttonText, Action buttonClickAction) : base(titleText, errorMessage, buttonText, buttonClickAction)
{
}
}
}

519
TechbloxModdingAPI/Block.cs Normal file
View file

@ -0,0 +1,519 @@
using System;
using System.Collections.Generic;
using DataLoader;
using Gamecraft.Blocks.BlockGroups;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using RobocraftX.Common;
using RobocraftX.Blocks;
using Unity.Mathematics;
using HarmonyLib;
using RobocraftX.PilotSeat;
using RobocraftX.Rendering;
using Techblox.BlockLabelsServer;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI
{
/// <summary>
/// A single (perhaps scaled) block. Properties may return default values if the block is removed and then setting them is ignored.
/// For specific block type operations, use the specialised block classes in the TechbloxModdingAPI.Blocks namespace.
/// </summary>
public class Block : EcsObjectBase, IEquatable<Block>, IEquatable<EGID>
{
protected static readonly PlacementEngine PlacementEngine = new PlacementEngine();
protected static readonly MovementEngine MovementEngine = new MovementEngine();
protected static readonly RotationEngine RotationEngine = new RotationEngine();
protected static readonly RemovalEngine RemovalEngine = new RemovalEngine();
protected static readonly SignalEngine SignalEngine = new SignalEngine();
protected static readonly BlockEventsEngine BlockEventsEngine = new BlockEventsEngine();
protected static readonly ScalingEngine ScalingEngine = new ScalingEngine();
protected static readonly BlockCloneEngine BlockCloneEngine = new BlockCloneEngine();
protected internal static readonly BlockEngine BlockEngine = new BlockEngine();
/// <summary>
/// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position.
/// Place blocks next to each other to connect them.
/// The placed block will be a complete block with a placement grid and collision which will be saved along with the game.
/// </summary>
/// <param name="block">The block's type</param>
/// <param name="position">The block's position - default block size is 0.2</param>
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param>
/// <param name="player">The player who placed the block</param>
/// <returns>The placed block or null if failed</returns>
public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null)
{
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{
var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire);
var egid = initializer.EGID;
var bl = New(egid);
bl.InitData = initializer;
Placed += bl.OnPlacedInit;
return bl;
}
return null;
}
/// <summary>
/// Returns the most recently placed block.
/// </summary>
/// <returns>The block object or null if doesn't exist</returns>
public static Block GetLastPlacedBlock()
{
uint lastBlockID = CommonExclusiveGroups.blockIDGeneratorClient.Peek() - 1;
EGID? egid = BlockEngine.FindBlockEGID(lastBlockID);
return egid.HasValue ? New(egid.Value) : null;
}
/*public static Block CreateGhostBlock()
{
return BlockGroup._engine.BuildGhostChild();
}*/
/// <summary>
/// An event that fires each time a block is placed.
/// </summary>
public static event EventHandler<BlockPlacedRemovedEventArgs> Placed
{ //TODO: Rename and add instance version in 3.0
add => BlockEventsEngine.Placed += value;
remove => BlockEventsEngine.Placed -= value;
}
/// <summary>
/// An event that fires each time a block is removed.
/// </summary>
public static event EventHandler<BlockPlacedRemovedEventArgs> Removed
{
add => BlockEventsEngine.Removed += value;
remove => BlockEventsEngine.Removed -= value;
}
private static readonly Dictionary<ExclusiveBuildGroup, (Func<EGID, Block> Constructor, Type Type)> GroupToConstructor =
new Dictionary<ExclusiveBuildGroup, (Func<EGID, Block>, Type)>
{
{CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, (id => new DampedSpring(id), typeof(DampedSpring))},
{CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))},
{CommonExclusiveGroups.LOGIC_BLOCK_GROUP, (id => new LogicGate(id), typeof(LogicGate))},
{CommonExclusiveGroups.PISTON_BLOCK_GROUP, (id => new Piston(id), typeof(Piston))},
{CommonExclusiveGroups.SERVO_BLOCK_GROUP, (id => new Servo(id), typeof(Servo))},
{CommonExclusiveGroups.WHEELRIG_BLOCK_BUILD_GROUP, (id => new WheelRig(id), typeof(WheelRig))}
};
static Block()
{
foreach (var group in SeatGroups.SEATS_BLOCK_GROUPS) // Adds driver and passenger seats, occupied and unoccupied
GroupToConstructor.Add(group, (id => new Seat(id), typeof(Seat)));
}
/// <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)
{
Type expectedType;
if (GroupToConstructor.ContainsKey(id.groupID) &&
!GetType().IsAssignableFrom(expectedType = GroupToConstructor[id.groupID].Type))
throw new BlockSpecializationException($"Incorrect block type! Expected: {expectedType} Actual: {GetType()}");
}
/// <summary>
/// This overload searches for the correct group the block is in.
/// It will throw an exception if the block doesn't exist.
/// Use the EGID constructor where possible or subclasses of Block as those specify the group.
/// </summary>
public Block(uint id) : this(BlockEngine.FindBlockEGID(id)
?? throw new BlockTypeException(
"Could not find the appropriate group for the block." +
" The block probably doesn't exist or hasn't been submitted."))
{
}
/// <summary>
/// Places a new block in the world.
/// </summary>
/// <param name="type">The block's type</param>
/// <param name="position">The block's position (a block is 0.2 wide in terms of position)</param>
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param>
/// <param name="player">The player who placed the block</param>
public Block(BlockIDs type, float3 position, bool autoWire = false, Player player = null)
: base(block =>
{
if (!PlacementEngine.IsInGame || !GameState.IsBuildMode())
throw new BlockException("Blocks can only be placed in build mode.");
var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire);
block.InitData = initializer;
Placed += ((Block)block).OnPlacedInit;
return initializer.EGID;
})
{
}
private EGID copiedFrom;
/// <summary>
/// The block's current position or zero if the block no longer exists.
/// A block is 0.2 wide by default in terms of position.
/// </summary>
public float3 Position
{
get => MovementEngine.GetPosition(this);
set
{
MovementEngine.MoveBlock(this, value);
if (blockGroup != null)
blockGroup.PosAndRotCalculated = false;
BlockEngine.UpdateDisplayedBlock(Id);
}
}
/// <summary>
/// The block's current rotation in degrees or zero if the block doesn't exist.
/// </summary>
public float3 Rotation
{
get => RotationEngine.GetRotation(this);
set
{
RotationEngine.RotateBlock(this, value);
if (blockGroup != null)
blockGroup.PosAndRotCalculated = false;
BlockEngine.UpdateDisplayedBlock(Id);
}
}
/// <summary>
/// The block's non-uniform scale or zero if the block's invalid. Independent of the uniform scaling.
/// The default scale of 1 means 0.2 in terms of position.
/// </summary>
public float3 Scale
{
get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale;
set
{
int uscale = UniformScale;
if (value.x < 4e-5) value.x = uscale;
if (value.y < 4e-5) value.y = uscale;
if (value.z < 4e-5) value.z = uscale;
BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale = value;
//BlockEngine.GetBlockInfo<GridScaleStruct>(this).gridScale = value - (int3) value + 1;
if (!Exists) return; //UpdateCollision needs the block to exist
ScalingEngine.UpdateCollision(Id);
BlockEngine.UpdateDisplayedBlock(Id);
}
}
/// <summary>
/// The block's uniform scale or zero if the block's invalid. Also sets the non-uniform scale.
/// The default scale of 1 means 0.2 in terms of position.
/// </summary>
public int UniformScale
{
get => BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(this).scaleFactor;
set
{
if (value < 1) value = 1;
BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(this).scaleFactor = value;
Scale = new float3(value, value, value);
}
}
/**
* Whether the block is flipped.
*/
public bool Flipped
{
get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale.x < 0;
set
{
ref var st = ref BlockEngine.GetBlockInfo<ScalingEntityStruct>(this);
st.scale.x = math.abs(st.scale.x) * (value ? -1 : 1);
BlockEngine.UpdatePrefab(this, (byte) Material, value);
}
}
/// <summary>
/// The block's type (ID). Returns BlockIDs.Invalid if the block doesn't exist anymore.
/// </summary>
public BlockIDs Type
{
get
{
var opt = BlockEngine.GetBlockInfoOptional<DBEntityStruct>(this);
return opt ? (BlockIDs) opt.Get().DBID : BlockIDs.Invalid;
}
}
/// <summary>
/// The block's color. Returns BlockColors.Default if the block no longer exists.
/// </summary>
public BlockColor Color
{
get
{
var opt = BlockEngine.GetBlockInfoOptional<ColourParameterEntityStruct>(this);
return new BlockColor(opt ? opt.Get().indexInPalette : byte.MaxValue);
}
set
{
if (value.Color == BlockColors.Default)
value = new BlockColor(FullGameFields._dataDb.TryGetValue((int) Type, out CubeListData cld)
? cld.DefaultColour
: throw new BlockTypeException("Unknown block type! Could not set default color."));
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this);
color.indexInPalette = value.Index;
color.hasNetworkChange = true;
color.paletteColour = BlockEngine.ConvertBlockColor(color.indexInPalette); //Setting to 255 results in black
BlockEngine.UpdateBlockColor(Id);
}
}
/// <summary>
/// The block's exact color. Gets reset to the palette color (Color property) after reentering the game.
/// </summary>
public float4 CustomColor
{
get => BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this).paletteColour;
set
{
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this);
color.paletteColour = value;
color.hasNetworkChange = true;
}
}
/**
* The block's material.
*/
public BlockMaterial Material
{
get
{
var opt = BlockEngine.GetBlockInfoOptional<CubeMaterialStruct>(this);
return opt ? (BlockMaterial) opt.Get().materialId : BlockMaterial.Default;
}
set
{
byte val = (byte) value;
if (value == BlockMaterial.Default)
val = FullGameFields._dataDb.TryGetValue((int) Type, out CubeListData cld)
? cld.DefaultMaterialID
: throw new BlockTypeException("Unknown block type! Could not set default material.");
if (!FullGameFields._dataDb.ContainsKey<MaterialPropertiesData>(val))
throw new BlockException($"Block material {value} does not exist!");
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
}
}
/// <summary>
/// The text displayed on the block if applicable, or null.
/// Setting it is temporary to the session, it won't be saved.
/// </summary>
[TestValue(null)]
public string Label
{
get
{
var opt = BlockEngine.GetBlockInfoOptional<LabelResourceIDComponent>(this);
return opt ? FullGameFields._managers.blockLabelResourceManager.GetText(opt.Get().instanceID) : null;
}
set
{
var opt = BlockEngine.GetBlockInfoOptional<LabelResourceIDComponent>(this);
if (opt) FullGameFields._managers.blockLabelResourceManager.SetText(opt.Get().instanceID, value);
}
}
private BlockGroup blockGroup;
/// <summary>
/// Returns the block group this block is a part of. Block groups can also be placed using blueprints.
/// Returns null if not part of a group, although all blocks should have their own by default.<br />
/// Setting the group after the block has been initialized will not update everything properly,
/// so you can only set this property on blocks newly placed by your code.<br />
/// To set it for existing blocks, you can use the Copy() method and set the property on the resulting block
/// (and remove this block).
/// </summary>
public BlockGroup BlockGroup
{
get
{
if (blockGroup != null) return blockGroup;
if (!GameState.IsBuildMode()) return null; // Breaks in simulation
var bgec = BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this);
return blockGroup = bgec.currentBlockGroup == -1
? null
: GetInstance(new EGID((uint)bgec.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup),
egid => new BlockGroup((int)egid.entityID, this));
}
set
{
if (Exists)
{
Logging.LogWarning("Attempted to set group of existing block. This is not supported."
+ " Copy the block and set the group of the resulting block.");
return;
}
blockGroup?.RemoveInternal(this);
if (!InitData.Valid)
return;
BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this).currentBlockGroup = (int?) value?.Id.entityID ?? -1;
value?.AddInternal(this);
blockGroup = value;
}
}
/// <summary>
/// Whether the block should be static in simulation. If set, it cannot be moved. The effect is temporary, it will not be saved with the block.
/// </summary>
public bool Static
{
get => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic;
set => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic = value;
}
/// <summary>
/// The mass of the block.
/// </summary>
public float Mass
{
get => BlockEngine.GetBlockInfo<MassStruct>(this).mass;
}
/// <summary>
/// Block complexity used for build rules. Determines the 'cost' of the block.
/// </summary>
public BlockComplexity Complexity
{
get => new(BlockEngine.GetBlockInfo<BlockComplexityComponent>(this));
set => BlockEngine.GetBlockInfo<BlockComplexityComponent>(this) = value;
}
/// <summary>
/// Whether the block exists. The other properties will return a default value if the block doesn't exist.
/// If the block was just placed, then this will also return false but the properties will work correctly.
/// </summary>
public bool Exists => BlockEngine.BlockExists(Id);
/// <summary>
/// Returns an array of blocks that are connected to this one. Returns an empty array if the block doesn't exist.
/// </summary>
public Block[] GetConnectedCubes() => BlockEngine.GetConnectedBlocks(Id);
/// <summary>
/// Removes this block.
/// </summary>
/// <returns>True if the block exists and could be removed.</returns>
public bool Remove() => RemovalEngine.RemoveBlock(Id);
/// <summary>
/// Returns the rigid body of the chunk of blocks this one belongs to during simulation.
/// Can be used to apply forces or move the block around while the simulation is running.
/// </summary>
/// <returns>The SimBody of the chunk or null if the block doesn't exist or not in simulation mode.</returns>
public SimBody GetSimBody()
{
var st = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(this);
/*return st.machineRigidBodyId != uint.MaxValue
? new SimBody(st.machineRigidBodyId, st.clusterId) - TODO:
: null;*/
return null;
}
/// <summary>
/// Creates a copy of the block in the game with the same properties, stats and wires.
/// </summary>
/// <returns></returns>
public Block Copy()
{
var block = PlaceNew(Type, Position);
block.Rotation = Rotation;
block.Color = Color;
block.Material = Material;
block.UniformScale = UniformScale;
block.Scale = Scale;
block.copiedFrom = Id;
return block;
}
private void OnPlacedInit(object sender, BlockPlacedRemovedEventArgs e)
{ //Member method instead of lambda to avoid constantly creating delegates
if (e.ID != Id) return;
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
if (copiedFrom != default)
BlockCloneEngine.CopyBlockStats(copiedFrom, Id);
}
public override string ToString()
{
return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Type)}: {Type}, {nameof(Color)}: {Color}, {nameof(Exists)}: {Exists}";
}
public bool Equals(Block other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Id.Equals(other.Id);
}
public bool Equals(EGID other)
{
return Id.Equals(other);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Block) obj);
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
public static void Init()
{
GameEngineManager.AddGameEngine(PlacementEngine);
GameEngineManager.AddGameEngine(MovementEngine);
GameEngineManager.AddGameEngine(RotationEngine);
GameEngineManager.AddGameEngine(RemovalEngine);
GameEngineManager.AddGameEngine(BlockEngine);
GameEngineManager.AddGameEngine(BlockEventsEngine);
GameEngineManager.AddGameEngine(ScalingEngine);
GameEngineManager.AddGameEngine(SignalEngine);
GameEngineManager.AddGameEngine(BlockCloneEngine);
Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine
}
}
}

View file

@ -0,0 +1,217 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Gamecraft.Blocks.BlockGroups;
using Svelto.ECS;
using Unity.Mathematics;
using UnityEngine;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI
{
/// <summary>
/// A group of blocks that can be selected together. The placed version of blueprints. Dispose after usage.
/// </summary>
public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable
{
internal static BlueprintEngine _engine = new BlueprintEngine();
private readonly Block sourceBlock;
private readonly List<Block> blocks;
private float3 position, rotation;
internal bool PosAndRotCalculated;
internal BlockGroup(int id, Block block) : base(new EGID((uint)id,
BlockGroupExclusiveGroups.BlockGroupEntityGroup))
{
if (id == BlockGroupUtility.GROUP_UNASSIGNED)
throw new BlockException("Cannot create a block group for blocks without a group!");
sourceBlock = block;
blocks = new List<Block>(GetBlocks());
Block.Removed += OnBlockRemoved;
}
private void OnBlockRemoved(object sender, BlockPlacedRemovedEventArgs e)
{
//blocks.RemoveAll(block => block.Id == e.ID); - Allocation heavy
int index = -1;
for (int i = 0; i < blocks.Count; i++)
{
if (blocks[i].Id == e.ID)
{
index = i;
break;
}
}
if (index != -1) blocks.RemoveAt(index);
}
public void Dispose()
{
Block.Removed -= OnBlockRemoved;
}
/// <summary>
/// The position of the block group (center). Can only be used after initialization is complete.
/// </summary>
public float3 Position
{
get
{
if (!PosAndRotCalculated)
Refresh();
return position;
}
set
{
var diff = value - position;
foreach (var block in blocks)
block.Position += diff;
if (!PosAndRotCalculated) //The condition can only be true if a block has been added/removed manually
Refresh(); //So the blocks array is up to date
else
position += diff;
}
}
/// <summary>
/// The rotation of the block group. Can only be used after initialization is complete.
/// </summary>
public float3 Rotation
{
get
{
if (!PosAndRotCalculated)
Refresh();
return rotation;
}
set
{
var diff = value - rotation;
var qdiff = Quaternion.Euler(diff);
foreach (var block in blocks)
{
block.Rotation += diff;
block.Position = qdiff * block.Position;
}
if (!PosAndRotCalculated)
Refresh();
else
rotation += diff;
}
}
/*/// <summary>
/// Removes all of the blocks in this group from the world.
/// </summary>
public void RemoveBlocks()
{
_engine.RemoveBlockGroup(Id); - TODO: Causes a hard crash
}*/
/// <summary>
/// Creates a new block group consisting of a single block.
/// You can add more blocks using the Add() method or by setting the BlockGroup property of the blocks.<br />
/// Note that only newly placed blocks can be added to groups.
/// </summary>
/// <param name="block">The block to add</param>
/// <returns>A new block group containing the given block</returns>
public static BlockGroup Create(Block block)
{
var bg = new BlockGroup(_engine.CreateBlockGroup(block.Position, Quaternion.Euler(block.Rotation)), block);
block.BlockGroup = bg;
return bg;
}
/// <summary>
/// Collects each block that is a part of this group. Also sets the position and rotation.
/// </summary>
/// <returns>An array of blocks</returns>
private Block[] GetBlocks()
{
if (!sourceBlock.Exists) return new[] {sourceBlock}; //The block must exist to get the others
var ret = _engine.GetBlocksFromGroup(sourceBlock.Id, out var pos, out var rot);
position = pos;
rotation = ((Quaternion) rot).eulerAngles;
PosAndRotCalculated = true;
return ret;
}
private void Refresh()
{
blocks.Clear();
blocks.AddRange(GetBlocks());
}
internal static void Init()
{
GameEngineManager.AddGameEngine(_engine);
}
public IEnumerator<Block> GetEnumerator() => blocks.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => blocks.GetEnumerator();
/// <summary>
/// Adds a block to the group. You can only add newly placed blocks
/// so that the game initializes the group membership properly.
/// </summary>
/// <param name="item"></param>
/// <exception cref="NullReferenceException"></exception>
public void Add(Block item)
{
if (item == null) throw new NullReferenceException("Cannot add null to a block group");
item.BlockGroup = this; //Calls AddInternal
}
internal void AddInternal(Block item)
{
blocks.Add(item);
_engine.AddBlockToGroup(item.Id, (int) Id.entityID);
}
/// <summary>
/// Removes all blocks from this group.
/// You cannot remove blocks that have been initialized, only those that you placed recently.
/// </summary>
public void Clear()
{
while (blocks.Count > 0)
Remove(blocks[blocks.Count - 1]);
}
public bool Contains(Block item) => blocks.Contains(item);
public void CopyTo(Block[] array, int arrayIndex) => blocks.CopyTo(array, arrayIndex);
/// <summary>
/// Removes a block from this group.
/// You cannot remove blocks that have been initialized, only those that you placed recently.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
/// <exception cref="NullReferenceException"></exception>
public bool Remove(Block item)
{
if (item == null) throw new NullReferenceException("Cannot remove null from a block group");
bool ret = item.BlockGroup == this;
if (ret)
item.BlockGroup = null; //Calls RemoveInternal
return ret;
}
internal void RemoveInternal(Block item) => blocks.Remove(item);
public int Count => blocks.Count;
public bool IsReadOnly { get; } = false;
public Block this[int index] => blocks[index]; //Setting is not supported, since the order doesn't matter
public override string ToString()
{
return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}, {nameof(Count)}: {Count}";
}
}
}

View file

@ -0,0 +1,64 @@
using System;
using Unity.Mathematics;
namespace TechbloxModdingAPI.Blocks
{
public struct BlockColor
{
public BlockColors Color => Index == byte.MaxValue
? BlockColors.Default
: (BlockColors) (Index % 10);
public byte Darkness => (byte) (Index == byte.MaxValue
? 0
: Index / 10);
public byte Index { get; }
public BlockColor(byte index)
{
if (index > 99 && index != byte.MaxValue)
throw new ArgumentOutOfRangeException(nameof(index), "Invalid color index. Must be 0-90 or 255.");
Index = index;
}
public BlockColor(BlockColors color, byte darkness = 0)
{
if (darkness > 9)
throw new ArgumentOutOfRangeException(nameof(darkness), "Darkness must be 0-9 where 0 is default.");
if (color > BlockColors.Red && color != BlockColors.Default) //Last valid color
throw new ArgumentOutOfRangeException(nameof(color), "Invalid color!");
Index = (byte) (darkness * 10 + (byte) color);
}
public static implicit operator BlockColor(BlockColors color)
{
return new BlockColor(color);
}
public float4 RGBA => Block.BlockEngine.ConvertBlockColor(Index);
public override string ToString()
{
return $"{nameof(Color)}: {Color}, {nameof(Darkness)}: {Darkness}";
}
}
/// <summary>
/// Preset block colours
/// </summary>
public enum BlockColors : byte
{
Default = byte.MaxValue,
White = 0,
Pink,
Purple,
Blue,
Aqua,
Green,
Lime,
Yellow,
Orange,
Red
}
}

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

@ -0,0 +1,65 @@
using System;
using TechbloxModdingAPI;
namespace TechbloxModdingAPI.Blocks
{
public class BlockException : TechbloxModdingAPIException
{
public BlockException()
{
}
public BlockException(System.String message) : base(message)
{
}
public BlockException(System.String message, Exception innerException) : base(message, innerException)
{
}
}
public class BlockTypeException : BlockException
{
public BlockTypeException()
{
}
public BlockTypeException(string message) : base(message)
{
}
}
public class BlockSpecializationException : BlockException
{
public BlockSpecializationException()
{
}
public BlockSpecializationException(string message) : base(message)
{
}
}
public class WiringException : BlockException
{
public WiringException()
{
}
public WiringException(string message) : base(message)
{
}
}
public class WireInvalidException : WiringException
{
public WireInvalidException()
{
}
public WireInvalidException(string message) : base(message)
{
}
}
}

View file

@ -0,0 +1,383 @@
namespace TechbloxModdingAPI.Blocks
{
/// <summary>
/// Possible block types
/// </summary>
public enum BlockIDs : ushort
{
/// <summary>
/// Called "nothing" in Techblox. (DBID.NOTHING)
/// </summary>
Invalid = ushort.MaxValue,
Cube = 0,
Wedge,
QuarterPyramid,
Tetrahedron,
RoundedWedge,
RoundedQuarterPyramid,
RoundedTetrahedron,
NegativeQuarterPyramid,
NegativeTetrahedron,
RoundedNegativeQuarterPyramid,
RoundedNegativeTetrahedron,
Plate,
PlateWedge,
PlateQuarterPyramid,
PlateTetrahedron,
Sphere,
CarWheelArch = 47,
CarArchSmallFlare,
CarArchFlare,
CarArchExtrudedFlare,
Axle = 100,
Hinge,
BallJoint,
UniversalJoint,
TelescopicJoint,
DampedHingeSpring,
DampedAxleSpring,
DampedSpring,
WheelRigNoSteering,
WheelRigWithSteering,
PlateTriangle = 130,
PlateCircle,
PlateQuarterCircle,
PlateRoundedWedge,
PlateRoundedTetrahedron,
Cone,
ConeSegment,
DoubleSliced,
HalfDoubleSliced,
EighthPyramid,
Hemisphere,
WideCylinder,
WideCylinderBend,
WideCylinderT,
WideCylinderCross,
WideCylinderCorner,
NarrowCylinder,
NarrowCylinderBend,
NarrowCylinderT,
NarrowCylinderCross,
DriverSeat,
PassengerSeat,
Engine,
NarrowCylinderCorner,
PlateWideCylinder,
PlateNarrowCylinder,
PlateNegativeTetrahedron,
PlateNegativeQuarterPyramid,
PlateRoundedNegativeTetrahedron,
PlateRoundedNegativeQuarterPyramid,
HeadlampSquare,
HeadlampCircle,
HeadlampWedge,
WideCylinderDiagonal,
NarrowCylinderDiagonal,
HeadlampTetrahedron,
GoKartEngine,
Screen5X2Y2Z,
Screen5X2Y3Z,
Screen5X2Y5Z,
Screen9X2Y2Z,
Screen9X3Y2Z,
Screen9X2Y3Z,
Screen9X3Y3Z,
Screen9X2Y5Z,
Screen9X3Y5Z,
Screen11X3Y2Z,
Screen11X3Y3Z,
Screen11X3Y5Z,
Window6X2Y2Z,
Window6X3Y2Z,
Window6X2Y2ZS1,
Window6X3Y2ZS1,
Window6X2Y2ZS2,
Window6X3Y2ZS2,
Window6X2Y2ZS4,
Window6X3Y2ZS4,
FrameSquare,
FrameSkewedSquare,
FrameTriangle,
FrameSkewedTriangle,
GlassFrameSquare,
GlassFrameSkewedSquare,
GlassFrameTriangle,
GlassFrameSkewedTriangle,
GlassPlate,
GlassPlateTriangle,
GoKartWheelRigNoSteering,
GoKartWheelRigWithSteering,
GoKartSeat,
CarWheelWideProfile,
CarWheel,
GoKartWheelWideProfile,
GoKartWheel,
ANDLogicGate,
ORLogicGate,
NOTLogicGate,
NANDLogicGate,
NORLogicGate,
XORLogicGate,
XNORLogicGate,
AdderMathBlock,
SubtractorMathBlock,
MultiplierMathBlock,
DividerMathBlock,
InverterMathBlock,
AverageMathBlock,
AbsoluteMathBlock,
MinMathBlock,
MaxMathBlock,
SimpleConnector,
Motor,
AxleServo,
HingeServo,
Piston,
Button,
Switch,
Dial,
Lever,
ThreeWaySwitch,
EqualsMathBlock,
LessThanMathBlock,
LessThanOrEqualMathBlock,
GreaterThanMathBlock,
GreaterThanOrEqualMathBlock,
HatchbackWheelRigNoSteering,
HatchbackWheelRigWithSteering,
HatchbackEngine,
HatchbackWheel,
HatchbackWheelArch,
HatchbackArchSmallFlare,
HatchbackArchFlare,
CeilingStripLight,
CardboardBox,
BarrierRail,
BarrierRailEnd,
TruckWheel,
HatchbackWheelWideProfile,
TruckWheelRigWithSteering = 249,
TruckWheelRigNoSteering,
HatchbackDriverSeat,
HatchbackPassengerSeat,
FormulaEngine,
SmallGrass,
SmallGrassRoad,
GrassBridge,
SmallGrassTurn,
MediumGrassTurn,
LargeGrassTurn,
ExtraLargeGrassTurn,
TruckWheelDouble,
TruckWheelArch,
TruckArchSingleFlare,
WoodenDoorWithWindow,
TyreBarrierCorner,
TyreBarrierEdge,
TyreBarrierCenter,
AppleTree,
AppleForestTree,
FormulaWheel,
FormulaWheelRear,
AppleSapling,
GrassHill,
GrassHillInnerCorner,
GrassHillOuterCorner,
GrassRoadHill,
FormulaSeat,
SmallDirt,
SmallDirtRoad,
SmallDirtTurn,
MediumDirtTurn,
LargeDirtTurn,
ExtraLargeDirtTurn,
SmallGrid,
MonsterTruckWheel,
SmallGrassGridStart,
SmallGrassRumbleStripRoad,
SmallGrassRumbleStripEndRoad,
SmallGrassStartLine,
MonsterTruckEngine,
DirtHill,
DirtHillInnerCorner,
DirtHillOuterCorner,
BuildingWindowEdge,
BuildingWindowCorner,
BuildingWindowStraight,
BuildingWindowTJunction,
BuildingWindowCross,
BuildingWindowEdgeSill,
BuildingWindowCornerSill,
BuildingWindowTJunctionSill,
Broadleaf,
ForestBroadleaf,
AzaleaBush,
AzaleaFlowers1,
AzaleaFlowers2,
TreeStump1,
TreeStump2,
FieldJuniper,
ForestJuniper,
JuniperSapling,
JuniperSeedling,
FieldRedMaple,
RedMapleForest1,
RedMapleForest2,
RedMapleSapling,
FieldWhiteSpruce,
ForestWhiteSpruce,
WhiteSpruceSapling,
GirderBase,
GirderStraight,
GirderDiagonal,
GirderCorner,
PostBase,
PostStraight,
PostLShape,
PostTJunction,
PostCross,
PostCorner,
PostDiagonal,
DirtRock1,
DirtRock2,
DirtRock3,
DirtRock4,
DirtRoadHill,
WoodenPalette,
ElderberryBush,
BarrelCactus,
KnapweedFlower,
MarigoldFlowers,
TrampledBushyBluestep,
RoughGrass,
DogRose,
WesternSwordFern,
BackyardGrass,
ThickGrass,
FireExtinguisher,
DirtLowRamp,
DirtTabletopRamp,
MonsterTruckWheelRigNoSteering,
MonsterTruckWheelRigWithSteering,
MeadowCloudyDayAtmosphere,
BarrierRailDiagonal,
DirtHighRamp,
GrassRock1,
GrassRock2,
GrassRock3,
GrassRock4,
GreenFieldsSunnyDayAtmosphere,
RedMountainsDawnAtmosphere,
HighFantasySunriseAtmosphere,
/// <summary>
/// The grid block used by the world editor, named Small Grid like the other one
/// </summary>
SmallGridInWorldEditor,
CityDoubleCrossing,
CityDoubleCrossroads,
CitySmallDoubleJunction,
CityDoubleJunction,
CityDoubleToSingleJunction,
CitySmallDoubleRoad,
CityDoubleRoad,
CitySmallDoubleTurn,
CityLargeDoubleTurn,
CitySmallSingleTurn,
CityLargeSingleTurn,
CitySingleJunction,
CitySingleRoad,
SegoeUITextblock,
GravtracTextblock,
HauserTextblock,
TechnopollasTextblock,
CityDoubleHillRoad,
DiagonalTrackTile,
DiagonalTrackTile2,
DiagonalTransitionTile,
SplitLane,
BitBlock,
Timer,
CityNightAtmosphere,
FloodLight,
SoccerBall,
CircularWallLight,
BlueSkyAtmos,
DirtToGrassTransitionTile = 393,
DirtToGrassTransitionInnerTile,
DirtToGrassTransitionOuterTile,
DirtToGrassTransitionHillTile,
DirtToGrassTransitionRoadTile,
DirtHill2 = 399,
DirtHill3,
DirtInnerCorner2 = 402,
DirtInnerCorner3,
DirtOuterCorner2 = 405,
DirtOuterCorner3,
CityTarmacEdgeInner,
CityTarmacEdgeOuter,
CityTarmacEdgeRoad,
CityTarmac,
SmallGrassQuarterTile,
CityToRacetrackTransition,
HUDTimer,
CentreHUD,
Checkpoint,
ScoreboardHUD,
GameplaySFX,
SpawnPoint,
AreaSensor,
WorldResetter,
SmallJet,
MediumJet,
LargeJet,
DistanceSensor,
Stabilizer,
ObjectID,
ScoreToTechpointConversion,
TeamScore,
ScorePickupBlock,
SportyHatchbackDriverSeat,
SportyHatchbackPassengerSeat,
FlamingExhaust = 433,
SmokingExhaust,
StreetLamp,
Vector7HatchbackWheel,
Vector7HatchbackWheelWideProfile,
Vector7SedanWheel,
Vector7SedanWideProfile,
Vector7FormulaWheel,
Vector7FormulaWheelRear,
Vector7MonsterTruckWheel,
Vector7TruckWheel,
Vector7TruckWheelDouble,
BusSeat,
XLJet,
XXLJet,
ElectricSedanEngine,
HeadlampIndicator,
HeadlampSrip,
HeadlampStripEdge,
ConstantBlock,
CounterBlock,
SmallGridHill,
SmallGridHillInnerCorner,
SmallGridHillOuterCorner,
AimingAxleServo,
AimingHingeServo,
WeaponDisabler,
Vector7SmallJet,
Vector7MediumJet,
Vector7LargeJet,
Vector7XLJet,
Vector7XXLJet,
APCWheelRigNoSteering,
APCWheelRigWithSteering,
APCWheel,
APCSeat,
APCEngine,
DamageScoreBlock,
KillScoreBlock,
Autocannon = 480
}
}

View file

@ -0,0 +1,46 @@
namespace TechbloxModdingAPI.Blocks
{
public enum BlockMaterial : byte
{
Default = byte.MaxValue,
SteelBodywork = 0,
RigidSteel,
CarbonFiber,
Plastic,
Wood = 6,
RigidSteelPainted,
RigidSteelRustedPaint,
RigidSteelHeavyRust,
SteelBodyworkMetallicPaint,
SteelBodyworkRustedPaint,
SteelBodyworkHeavyRust,
WoodVarnishedDark,
Chrome,
FenceChainLink,
ConcreteUnpainted,
Grid9x9,
CeramicTileFloor,
PlasticBumpy,
PlasticDustySmeared,
AluminiumGarageDoor,
SteelRigidScratched,
AluminiumBrushedTinted,
AluminiumSheetStained,
ConcretePaintedGrooves,
PlasticSpecklySatin,
SteelBodyworkPaintedChipped,
WoodPainted,
WoodRoughGrungy,
Boundary,
Emissive,
AircraftPanelingRivetedPainted,
AircraftPanelingRivetedMetallic,
SteelBodyworkPearlescent,
SteelBodyworkRadWrap,
SteelBodyworkGlitter,
BouncyRubber,
BouncyRubberTieDye,
BrickPainted,
FuturisticPanelingRivetedPainted,
}
}

View file

@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using DataLoader;
using Svelto.Tasks;
using Unity.Mathematics;
using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks
{
#if TEST
/// <summary>
/// Block test cases. Not accessible in release versions.
/// </summary>
[APITestClass]
public static class BlockTests
{
[APITestCase(TestType.Game)] //At least one block must be placed for simulation to work
public static void TestPlaceNew()
{
Block newBlock = Block.PlaceNew(BlockIDs.Cube, float3.zero);
Assert.NotNull(newBlock.Id, "Newly placed block is missing Id. This should be populated when the block is placed.", "Newly placed block Id is not null, block successfully placed.");
}
[APITestCase(TestType.EditMode)]
public static void TestInitProperty()
{
Block newBlock = Block.PlaceNew(BlockIDs.Cube, float3.zero + 2);
if (!Assert.CloseTo(newBlock.Position, (float3.zero + 2), $"Newly placed block at {newBlock.Position} is expected at {Unity.Mathematics.float3.zero + 2}.", "Newly placed block position matches.")) return;
//Assert.Equal(newBlock.Exists, true, "Newly placed block does not exist, possibly because Sync() skipped/missed/failed.", "Newly placed block exists, Sync() successful.");
}
[APITestCase(TestType.EditMode)]
public static void TestBlockIDCoverage()
{
Assert.Equal(
FullGameFields._dataDb.GetValues<CubeListData>().Keys.Select(ushort.Parse).OrderBy(id => id)
.SequenceEqual(Enum.GetValues(typeof(BlockIDs)).Cast<ushort>().OrderBy(id => id)
.Except(new[] {(ushort) BlockIDs.Invalid})), true,
"Block ID enum is different than the known block types, update needed.",
"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)]
public static void TestBlockIDs()
{
float3 pos = new float3();
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;
try
{
blocks[i++] = Block.PlaceNew(id, pos);
pos += 0.2f;
}
catch (Exception e)
{ //Only print failed case
Assert.Fail($"Failed to place block type {id}: {e}");
return;
}
}
Assert.Pass("Placing all possible block types succeeded.");
}
[APITestCase(TestType.EditMode)]
public static IEnumerator<TaskContract> TestBlockProperties()
{ //Uses the result of the previous test case
yield return Yield.It;
if (blocks is null)
yield break;
for (var index = 0; index < blocks.Length; index++)
{
var block = blocks[index];
if (!block.Exists) continue;
foreach (var property in block.GetType().GetProperties())
{
//Includes specialised block properties
if (property.SetMethod == null) continue;
bool3 Float3Compare(float3 a, float3 b)
{ // From Unity reference code
return math.abs(b - a) < math.max(
0.000001f * math.max(math.abs(a), math.abs(b)),
float.Epsilon * 8
);
}
bool4 Float4Compare(float4 a, float4 b)
{ // From Unity reference code
return math.abs(b - a) < math.max(
0.000001f * math.max(math.abs(a), math.abs(b)),
float.Epsilon * 8
);
}
var testValues = new (Type, object, Predicate<(object Value, object Default)>)[]
{
//(type, default value, predicate or null for equality)
(typeof(long), 3, null),
(typeof(int), 4, null),
(typeof(double), 5.2f, t => Math.Abs((double) t.Value - (double) t.Default) < float.Epsilon),
(typeof(float), 5.2f, t => Math.Abs((float) t.Value - (float) t.Default) < float.Epsilon),
(typeof(bool), true, t => (bool) t.Value),
(typeof(string), "Test", t => (string) t.Value == "Test"), //String equality check
(typeof(float3), (float3) 20, t => math.all(Float3Compare((float3)t.Value, (float3)t.Default))),
(typeof(BlockColor), new BlockColor(BlockColors.Aqua, 2), null),
(typeof(float4), (float4) 5, t => math.all(Float4Compare((float4)t.Value, (float4)t.Default)))
};
var propType = property.PropertyType;
if (!propType.IsValueType) continue;
(object valueToUse, Predicate<(object Value, object Default)> predicateToUse) = (null, null);
foreach (var (type, value, predicate) in testValues)
{
if (type.IsAssignableFrom(propType))
{
valueToUse = value;
predicateToUse = predicate ?? (t => Equals(t.Value, t.Default));
break;
}
}
if (propType.IsEnum)
{
var values = propType.GetEnumValues();
valueToUse = values.GetValue(values.Length / 2);
predicateToUse = t => Equals(t.Value, t.Default);
}
if (valueToUse == null)
{
Assert.Fail($"Property {block.GetType().Name}.{property.Name} has an unknown type {propType}, test needs fixing.");
yield break;
}
try
{
property.SetValue(block, valueToUse);
}
catch (Exception e)
{
Assert.Fail($"Failed to set property {block.GetType().Name}.{property.Name} to {valueToUse}\n{e}");
}
object got;
try
{
got = property.GetValue(block);
}
catch (Exception e)
{
Assert.Fail($"Failed to get property {block.GetType().Name}.{property.Name}\n{e}");
continue;
}
var attr = property.GetCustomAttribute<TestValueAttribute>();
if (!predicateToUse((got, valueToUse)) && (attr == null || !Equals(attr.PossibleValue, got)))
{
Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}.");
yield break;
}
}
}
Assert.Pass("Setting all possible properties of all registered API block types succeeded.");
}
[APITestCase(TestType.EditMode)]
public static IEnumerator<TaskContract> TestDefaultValue()
{
for (int i = 0; i < 2; i++)
{ //Tests shared defaults
var block = Block.PlaceNew(BlockIDs.Cube, 1);
while (!block.Exists)
yield return Yield.It;
block.Remove();
while (block.Exists)
yield return Yield.It;
if(!Assert.Equal(block.Position, default,
$"Block position default value {block.Position} is incorrect, should be 0.",
$"Block position default value {block.Position} matches default."))
yield break;
block.Position = 4;
}
}
[APITestCase(TestType.EditMode)]
public static void TestDampedSpring()
{
Block newBlock = Block.PlaceNew(BlockIDs.DampedSpring, Unity.Mathematics.float3.zero + 1);
DampedSpring b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
Assert.Errorless(() => { b = (DampedSpring) newBlock; }, "Casting block to DampedSpring raised an exception: ", "Casting block to DampedSpring completed without issue.");
if (!Assert.CloseTo(b.Stiffness, 1f, $"DampedSpring.Stiffness {b.Stiffness} does not equal default value, possibly because it failed silently.", "DampedSpring.Stiffness is close enough to default.")) return;
if (!Assert.CloseTo(b.Damping, 0.1f, $"DampedSpring.Damping {b.Damping} does not equal default value, possibly because it failed silently.", "DampedSpring.Damping is close enough to default.")) return;
if (!Assert.CloseTo(b.MaxExtension, 0.3f, $"DampedSpring.MaxExtension {b.MaxExtension} does not equal default value, possibly because it failed silently.", "DampedSpring.MaxExtension is close enough to default.")) return;
}
/*[APITestCase(TestType.Game)]
public static void TestMusicBlock1()
{
Block newBlock = Block.PlaceNew(BlockIDs.MusicBlock, Unity.Mathematics.float3.zero + 2);
MusicBlock b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
Assert.Errorless(() => { b = newBlock.Specialise<MusicBlock>(); }, "Block.Specialize<MusicBlock>() raised an exception: ", "Block.Specialize<MusicBlock>() completed without issue.");
if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return;
if (!Assert.CloseTo(b.Volume, 100f, $"MusicBlock.Volume {b.Volume} does not equal default value, possibly because it failed silently.", "MusicBlock.Volume is close enough to default.")) return;
if (!Assert.Equal(b.TrackIndex, 0, $"MusicBlock.TrackIndex {b.TrackIndex} does not equal default value, possibly because it failed silently.", "MusicBlock.TrackIndex is equal to default.")) return;
_musicBlock = b;
}
private static MusicBlock _musicBlock;
[APITestCase(TestType.EditMode)]
public static void TestMusicBlock2()
{
//Block newBlock = Block.GetLastPlacedBlock();
var b = _musicBlock;
if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return;
b.IsPlaying = true; // play sfx
if (!Assert.Equal(b.IsPlaying, true, $"MusicBlock.IsPlaying {b.IsPlaying} does not equal true, possibly because it failed silently.", "MusicBlock.IsPlaying is set properly.")) return;
if (!Assert.Equal(b.ChannelType, ChannelType.None, $"MusicBlock.ChannelType {b.ChannelType} does not equal default value, possibly because it failed silently.", "MusicBlock.ChannelType is equal to default.")) return;
//Assert.Log(b.Track.ToString());
if (!Assert.Equal(b.Track.ToString(), new Guid("3237ff8f-f5f2-4f84-8144-496ca280f8c0").ToString(), $"MusicBlock.Track {b.Track} does not equal default value, possibly because it failed silently.", "MusicBlock.Track is equal to default.")) return;
}
[APITestCase(TestType.EditMode)]
public static void TestLogicGate()
{
Block newBlock = Block.PlaceNew(BlockIDs.NOTLogicBlock, Unity.Mathematics.float3.zero + 1);
LogicGate b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
Assert.Errorless(() => { b = newBlock.Specialise<LogicGate>(); }, "Block.Specialize<LogicGate>() raised an exception: ", "Block.Specialize<LogicGate>() completed without issue.");
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return;
if (!Assert.Equal(b.InputCount, 1u, $"LogicGate.InputCount {b.InputCount} does not equal default value, possibly because it failed silently.", "LogicGate.InputCount is default.")) return;
if (!Assert.Equal(b.OutputCount, 1u, $"LogicGate.OutputCount {b.OutputCount} does not equal default value, possibly because it failed silently.", "LogicGate.OutputCount is default.")) return;
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return;
//if (!Assert.Equal(b.PortName(0, true), "Input", $"LogicGate.PortName(0, input:true) {b.PortName(0, true)} does not equal default value, possibly because it failed silently.", "LogicGate.PortName(0, input:true) is close enough to default.")) return;
LogicGate target = null;
if (!Assert.Errorless(() => { target = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, Unity.Mathematics.float3.zero + 2); })) return;
Wire newWire = null;
if (!Assert.Errorless(() => { newWire = b.Connect(0, target, 0);})) return;
if (!Assert.NotNull(newWire, "SignalingBlock.Connect(...) returned null, possible because it failed silently.", "SignalingBlock.Connect(...) returned a non-null value.")) return;
}*/
/*[APITestCase(TestType.EditMode)]
public static void TestSpecialiseError()
{
Block newBlock = Block.PlaceNew(BlockIDs.Bench, new float3(1, 1, 1));
if (Assert.Errorful<BlockTypeException>(() => newBlock.Specialise<MusicBlock>(), "Block.Specialise<MusicBlock>() was expected to error on a bench block.", "Block.Specialise<MusicBlock>() errored as expected for a bench block.")) return;
}*/
}
#endif
}

View file

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

View file

@ -0,0 +1,382 @@
namespace TechbloxModdingAPI.Blocks
{
using RobocraftX.Common;
using Svelto.ECS;
public class Engine : SignalingBlock
{
/// <summary>
/// Constructs a(n) Engine object representing an existing block.
/// </summary>
public Engine(EGID egid) :
base(egid)
{
}
/// <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
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn = value;
}
}
/// <summary>
/// Gets or sets the Engine's CurrentGear property. May not be saved.
/// </summary>
public int CurrentGear
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentGear;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentGear = value;
}
}
/// <summary>
/// Gets or sets the Engine's GearChangeCountdown property. May not be saved.
/// </summary>
public float GearChangeCountdown
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).gearChangeCountdown;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).gearChangeCountdown = value;
}
}
/// <summary>
/// Gets or sets the Engine's CurrentRpmAV property. May not be saved.
/// </summary>
public float CurrentRpmAV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmAV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmAV = value;
}
}
/// <summary>
/// Gets or sets the Engine's CurrentRpmLV property. May not be saved.
/// </summary>
public float CurrentRpmLV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmLV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmLV = value;
}
}
/// <summary>
/// Gets or sets the Engine's TargetRpmAV property. May not be saved.
/// </summary>
public float TargetRpmAV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmAV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmAV = value;
}
}
/// <summary>
/// Gets or sets the Engine's TargetRpmLV property. May not be saved.
/// </summary>
public float TargetRpmLV
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmLV;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmLV = value;
}
}
/// <summary>
/// Gets or sets the Engine's CurrentTorque property. May not be saved.
/// </summary>
public float CurrentTorque
{
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

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Gamecraft.Wires;
using HarmonyLib;
using RobocraftX.Blocks;
using RobocraftX.Character;
using RobocraftX.Common;
using Svelto.DataStructures;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Blocks.Engines
{
public class BlockCloneEngine : IApiEngine
{
private static Type copyEngineType =
AccessTools.TypeByName("Gamecraft.GUI.Tweaks.Engines.CopyTweaksOnPickEngine");
private static Type copyWireEngineType =
AccessTools.TypeByName("Gamecraft.Wires.WireConnectionCopyOnPickEngine");
private static Type createWireEngineType =
AccessTools.TypeByName("RobocraftX.GUI.Wires.WireConnectionCreateOnPlaceEngine");
private MethodBase copyFromBlock = AccessTools.Method(copyEngineType, "CopyTweaksFromBlock");
private MethodBase copyToBlock = AccessTools.Method(copyEngineType, "ApplyTweaksToPlacedBlock");
private MethodBase copyWireFromBlock = AccessTools.Method(copyWireEngineType, "CopyWireInputsAndOutputs");
private MethodBase copyWireToBlock = AccessTools.Method(createWireEngineType, "PlaceWiresOnPlaceNewCube");
public void Ready()
{
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
}
public void CopyBlockStats(EGID sourceID, EGID targetID)
{
var allCharacters = (LocalFasterReadOnlyList<ExclusiveGroupStruct>) CharacterExclusiveGroups.AllCharacters;
foreach (var ((pickedBlockColl, count), _) in entitiesDB.QueryEntities<PickedBlockExtraDataStruct>(allCharacters))
{
for (int i = 0; i < count; ++i)
{
ref PickedBlockExtraDataStruct pickedBlock = ref pickedBlockColl[i];
var oldStruct = pickedBlock;
pickedBlock.pickedBlockEntityID = sourceID;
pickedBlock.placedBlockEntityID = targetID;
pickedBlock.placedBlockTweaksMustCopy = true;
if (entitiesDB.Exists<BlockTagEntityStruct>(pickedBlock.pickedBlockEntityID)
&& entitiesDB.Exists<BlockTagEntityStruct>(pickedBlock.placedBlockEntityID))
{
copyFromBlock.Invoke(Patch.copyEngine, new object[] {pickedBlock.ID, pickedBlock});
uint playerID = Player.LocalPlayer.Id;
var parameters = new object[] {playerID, pickedBlock};
copyWireFromBlock.Invoke(Patch.copyWireEngine, parameters);
pickedBlock = (PickedBlockExtraDataStruct) parameters[1]; //ref arg
copyToBlock.Invoke(Patch.copyEngine, new object[] {pickedBlock.ID, pickedBlock});
ExclusiveGroupStruct group = BuildModeWiresGroups.WIRES_COPY_GROUP + playerID;
copyWireToBlock.Invoke(Patch.createWireEngine, new object[] {group, pickedBlock.ID});
pickedBlock.placedBlockTweaksMustCopy = false;
}
pickedBlock = oldStruct; //Make sure to not interfere with the game - Although that might not be the case with the wire copying
}
}
}
[HarmonyPatch]
private static class Patch
{
public static object copyEngine;
public static object copyWireEngine;
public static object createWireEngine;
public static void Postfix(object __instance)
{
if (__instance.GetType() == copyEngineType)
copyEngine = __instance;
else if (__instance.GetType() == copyWireEngineType)
copyWireEngine = __instance;
else if (__instance.GetType() == createWireEngineType)
createWireEngine = __instance;
}
public static IEnumerable<MethodBase> TargetMethods()
{
return new[]
{
AccessTools.GetDeclaredConstructors(copyEngineType)[0],
AccessTools.GetDeclaredConstructors(copyWireEngineType)[0],
AccessTools.GetDeclaredConstructors(createWireEngineType)[0]
};
}
}
public string Name { get; } = "TechbloxModdingAPIBlockCloneGameEngine";
public bool isRemovable { get; } = false;
}
}

View file

@ -0,0 +1,292 @@
using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using Gamecraft.ColourPalette;
using Gamecraft.Wires;
using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.Physics;
using RobocraftX.Rendering;
using RobocraftX.Rendering.GPUI;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Svelto.ECS.Experimental;
using Svelto.ECS.Hybrid;
using Techblox.BuildingDrone;
using Techblox.ObjectIDBlockServer;
using Unity.Mathematics;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
using PrefabsID = RobocraftX.Common.PrefabsID;
namespace TechbloxModdingAPI.Blocks.Engines
{
/// <summary>
/// Engine for executing general block actions
/// </summary>
public partial class BlockEngine : IApiEngine
{
public string Name { get; } = "TechbloxModdingAPIBlockGameEngine";
public EntitiesDB entitiesDB { set; private get; }
public bool isRemovable => false;
public void Dispose()
{
}
public void Ready()
{
}
public Block[] GetConnectedBlocks(EGID blockID)
{
if (!BlockExists(blockID)) return Array.Empty<Block>();
Stack<EGID> cubeStack = new Stack<EGID>();
FasterList<EGID> cubes = new FasterList<EGID>(10);
var coll = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
foreach (var ((ecoll, count), _) in coll)
{
for(int i = 0; i < count; i++)
{
ecoll[i].areConnectionsAssigned = false;
}
}
//TODO: GetConnectedCubesUtility
/*ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubes,
(in GridConnectionsEntityStruct _) => false);*/
var ret = new Block[cubes.count];
for (int i = 0; i < cubes.count; i++)
ret[i] = Block.New(cubes[i]);
return ret;
}
public float4 ConvertBlockColor(byte index) => index == byte.MaxValue
? new float4(-1f, -1f, -1f, -1f)
: entitiesDB.QueryEntity<PaletteEntryEntityStruct>(index,
ColourPaletteExclusiveGroups.COLOUR_PALETTE_GROUP).Colour;
public OptionalRef<T> GetBlockInfoOptional<T>(Block block) where T : unmanaged, IEntityComponent
{
return entitiesDB.QueryEntityOptional<T>(block);
}
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);
}
internal ref T GetBlockInfo<T>(EcsObjectBase obj) where T : unmanaged, IEntityComponent
{
return ref entitiesDB.QueryEntityOrDefault<T>(obj);
}
public ref T GetBlockInfoViewComponent<T>(Block block) where T : struct, IEntityViewComponent
{
return ref entitiesDB.QueryEntityOrDefault<T>(block);
}
internal object GetBlockInfo(Block block, Type type, string name)
{
var opt = AccessTools.Method(typeof(NativeApiExtensions), "QueryEntityOptional",
new[] { typeof(EntitiesDB), typeof(EcsObjectBase), typeof(ExclusiveGroupStruct) }, new[] { type })
.Invoke(null, new object[] { entitiesDB, block, null });
var str = AccessTools.Property(opt.GetType(), "Value").GetValue(opt);
return AccessTools.Field(str.GetType(), name).GetValue(str);
}
internal void SetBlockInfo(Block block, Type type, string name, object value)
{
var opt = AccessTools.Method(typeof(BlockEngine), "GetBlockInfoOptional", generics: new[] { type })
.Invoke(this, new object[] { block });
var prop = AccessTools.Property(opt.GetType(), "Value");
var str = prop.GetValue(opt);
AccessTools.Field(str.GetType(), name).SetValue(str, value);
prop.SetValue(opt, str);
}
public void UpdateDisplayedBlock(EGID id)
{
if (!BlockExists(id)) return;
var pos = entitiesDB.QueryEntity<PositionEntityStruct>(id);
var rot = entitiesDB.QueryEntity<RotationEntityStruct>(id);
var scale = entitiesDB.QueryEntity<ScalingEntityStruct>(id);
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)
{
var prefabAssetIDOpt = entitiesDB.QueryEntityOptional<PrefabAssetIDComponent>(block);
uint prefabAssetID = prefabAssetIDOpt
? prefabAssetIDOpt.Get().prefabAssetID
: uint.MaxValue;
if (prefabAssetID == uint.MaxValue)
{
if (entitiesDB.QueryEntityOptional<BlockTagEntityStruct>(block)) //The block exists
throw new BlockException("Prefab asset ID not found for block " + block); //Set by the game
return;
}
uint prefabId =
PrefabsID.GetOrAddPrefabID((ushort) prefabAssetID, material, 1, flipped);
entitiesDB.QueryEntityOrDefault<GFXPrefabEntityStructGPUI>(block).prefabID = prefabId;
if (block.Exists)
{
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
}
public void UpdateBlockColor(EGID id)
{
entitiesDB.PublishEntityChangeDelayed<ColourParameterEntityStruct>(id);
}
public bool BlockExists(EGID blockID)
{
return entitiesDB.Exists<BlockTagEntityStruct>(blockID);
}
public SimBody[] GetSimBodiesFromID(byte id)
{
var ret = new FasterList<SimBody>(4);
var oids = entitiesDB.QueryEntitiesOptional<ObjectIdEntityStruct>(ObjectIDBlockExclusiveGroups.OBJECT_ID_BLOCK_GROUP);
EGIDMapper<GridConnectionsEntityStruct>? connections = null;
foreach (var oid in oids)
{
if (oid.Get().objectId != id) continue;
if (!connections.HasValue) //Would need reflection to get the group from the build group otherwise
connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(oid.EGID.groupID);
//var rid = connections.Value.Entity(tag.ID.entityID).machineRigidBodyId;
/*foreach (var rb in ret) - TODO
{
if (rb.Id.entityID == rid)
goto DUPLICATE; //Multiple Object Identifiers on one rigid body
}
ret.Add(new SimBody(rid));
DUPLICATE: ;*/
}
return ret.ToArray();
}
public SimBody[] GetConnectedSimBodies(uint id)
{
var (joints, count) = entitiesDB.QueryEntities<JointEntityStruct>(MachineSimulationGroups.JOINTS_GROUP);
var list = new FasterList<SimBody>(4);
for (int i = 0; i < count; i++)
{
ref var joint = ref joints[i];
if (joint.isBroken) continue;
/*if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB)); - TODO:
else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA));*/
}
return list.ToArray();
}
public SimBody[] GetClusterBodies(uint cid)
{
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
var bodies = new HashSet<uint>();
foreach (var ((coll, count), _) in groups)
{
for (var index = 0; index < count; index++)
{
var conn = coll[index];
/*if (conn.clusterId == cid) - TODO
bodies.Add(conn.machineRigidBodyId);*/
}
}
return bodies.Select(id => new SimBody(id, cid)).ToArray();
}
public EGID? FindBlockEGID(uint id)
{
var groups = entitiesDB.FindGroups<BlockTagEntityStruct>();
foreach (ExclusiveGroupStruct group in groups)
{
if (entitiesDB.Exists<BlockTagEntityStruct>(id, group))
return new EGID(id, group);
}
return null;
}
public Cluster GetCluster(uint sbid)
{
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
foreach (var ((coll, count), _) in groups)
{
for (var index = 0; index < count; index++)
{
var conn = coll[index];
//Static blocks don't have a cluster ID but the cluster destruction manager should have one
/*if (conn.machineRigidBodyId == sbid && conn.clusterId != uint.MaxValue) - TODO:
return new Cluster(conn.clusterId);*/
}
}
return null;
}
public Block[] GetBodyBlocks(uint sbid)
{
var groups = entitiesDB.FindGroups<GridConnectionsEntityStruct>();
groups = new QueryGroups(groups).Except(CommonExclusiveGroups.DISABLED_JOINTS_IN_SIM_GROUP).Evaluate().result;
var set = new HashSet<Block>();
foreach (var ((coll, tags, count), _) in entitiesDB.QueryEntities<GridConnectionsEntityStruct, BlockTagEntityStruct>(groups))
{
for (var index = 0; index < count; index++)
{
var conn = coll[index];
/*if (conn.machineRigidBodyId == sbid) - TODO
set.Add(Block.New(tags[index].ID));*/
}
}
return set.ToArray();
}
public ObjectID[] GetObjectIDsFromID(byte id)
{
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();
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using RobocraftX.Blocks;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks.Engines
{
public class BlockEventsEngine : IReactionaryEngine<BlockTagEntityStruct>
{
public WrappedHandler<BlockPlacedRemovedEventArgs> Placed;
public WrappedHandler<BlockPlacedRemovedEventArgs> Removed;
public void Ready()
{
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
}
public string Name { get; } = "TechbloxModdingAPIBlockEventsEngine";
public bool isRemovable { get; } = false;
public void Add(ref BlockTagEntityStruct entityComponent, EGID egid)
{
Placed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid});
}
public void Remove(ref BlockTagEntityStruct entityComponent, EGID egid)
{
Removed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid});
}
}
public struct BlockPlacedRemovedEventArgs
{
public EGID ID;
private Block block;
public Block Block => block ??= Block.New(ID);
}
}

View file

@ -0,0 +1,404 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Gamecraft.Blocks.BlockGroups;
using Gamecraft.GUI.Blueprints;
using HarmonyLib;
using RobocraftX.Blocks;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Common;
using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.CR.MachineEditing.BoxSelect.ClipboardOperations;
using RobocraftX.Physics;
using RobocraftX.Rendering;
using RobocraftX.Rendering.GPUI;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.DataStructures;
using Svelto.ECS.EntityStructs;
using Svelto.ECS.Native;
using Svelto.ECS.Serialization;
using Techblox.Blocks.Connections;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using Allocator = Svelto.Common.Allocator;
namespace TechbloxModdingAPI.Blocks.Engines
{
public class BlueprintEngine : IFactoryEngine
{
private readonly MethodInfo getBlocksFromGroup =
AccessTools.Method("RobocraftX.CR.MachineEditing.PlaceBlockUtility:GetBlocksSharingBlockgroup");
private NativeDynamicArray selectedBlocksInGroup;
private NativeHashSet<ulong> removedConnections = new NativeHashSet<ulong>();
private int addingToBlockGroup = -1;
private static readonly Type PlaceBlueprintUtilityType =
AccessTools.TypeByName("RobocraftX.CR.MachineEditing.PlaceBlueprintUtility");
private static readonly FieldInfo LocalBlockMap =
AccessTools.DeclaredField(PlaceBlueprintUtilityType, "_localBlockMap");
private static readonly MethodInfo BuildBlock = AccessTools.Method(PlaceBlueprintUtilityType, "BuildBlock");
private static readonly MethodInfo BuildWires = AccessTools.Method(PlaceBlueprintUtilityType, "BuildWires");
private static readonly Type SerializeGhostBlueprintType =
AccessTools.TypeByName("RobocraftX.CR.MachineEditing.BoxSelect.SerializeGhostChildrenOnAddEngine");
private static readonly MethodInfo SerializeGhostBlueprint =
AccessTools.Method(SerializeGhostBlueprintType, "SerializeClipboardGhostEntities");
private static NativeEntityRemove nativeBlockRemove;
private static NativeEntityRemove nativeConnectionRemove;
private static MachineGraphConnectionEntityFactory connectionFactory;
private static IEntityFunctions entityFunctions;
private static ClipboardSerializationDataResourceManager clipboardManager;
private static IEntitySerialization entitySerialization;
private static IEntityFactory entityFactory;
private static FasterList<EGID> globalBlockMap;
private static object SerializeGhostBlueprintInstance;
private static GhostChildEntityFactory BuildGhostBlueprintFactory;
public void Ready()
{
selectedBlocksInGroup = NativeDynamicArray.Alloc<EGID>(Allocator.Persistent);
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
selectedBlocksInGroup.Dispose();
}
public Block[] GetBlocksFromGroup(EGID blockID, out float3 pos, out quaternion rot)
{
var blockPos = default(float3);
var blockRot = default(quaternion);
var parameters = new object[] {blockID, selectedBlocksInGroup, entitiesDB, blockPos, blockRot};
getBlocksFromGroup.Invoke(null, parameters);
pos = (float3) parameters[3];
rot = (quaternion) parameters[4];
int count = selectedBlocksInGroup.Count<EGID>();
var ret = new Block[count];
for (uint i = 0; i < count; i++)
ret[i] = Block.New(selectedBlocksInGroup.Get<EGID>(i));
selectedBlocksInGroup.FastClear();
return ret;
}
public void RemoveBlockGroup(int id)
{
BlockGroupUtility.RemoveAllBlocksInBlockGroup(id, entitiesDB, removedConnections, nativeBlockRemove,
nativeConnectionRemove, connectionFactory, default).Complete();
}
public int CreateBlockGroup(float3 position, quaternion rotation)
{
int nextFilterId = BlockGroupUtility.NextFilterId;
Factory.BuildEntity<BlockGroupEntityDescriptor>((uint) nextFilterId,
BlockGroupExclusiveGroups.BlockGroupEntityGroup).Init(new BlockGroupTransformEntityComponent
{
blockGroupGridRotation = rotation,
blockGroupGridPosition = position
});
return nextFilterId;
}
public void AddBlockToGroup(EGID blockID, int groupID)
{
if (globalBlockMap == null)
globalBlockMap = FullGameFields._deserialisedBlockMap;
if (groupID != addingToBlockGroup)
{
Logging.MetaDebugLog("Changing current block group from " + addingToBlockGroup + " to " + groupID);
addingToBlockGroup = groupID;
globalBlockMap.Clear();
}
globalBlockMap.Add(blockID);
}
public void SelectBlueprint(uint resourceID)
{
if (resourceID == uint.MaxValue)
BlueprintUtil.UnselectBlueprint(entitiesDB);
else
BlueprintUtil.SelectBlueprint(entitiesDB, resourceID, false, -1);
}
public uint CreateBlueprint()
{
uint index = clipboardManager.AllocateSerializationData();
return index;
}
public void ReplaceBlueprint(uint playerID, uint blueprintID, ICollection<Block> selected, float3 pos, quaternion rot)
{
var blockIDs = new EGID[selected.Count];
using (var enumerator = selected.GetEnumerator())
{
for (var i = 0; enumerator.MoveNext(); i++)
{
var block = enumerator.Current;
blockIDs[i] = block.Id;
}
}
var serializationData = clipboardManager.GetSerializationData(blueprintID);
SelectionSerializationUtility.ClearClipboard(playerID, entitiesDB, entityFunctions, serializationData.blueprintData, -1);
if (selected.Count == 0)
return;
//ref BlockGroupTransformEntityComponent groupTransform = ref EntityNativeDBExtensions.QueryEntity<BlockGroupTransformEntityComponent>(entitiesDb, (uint) local1.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup);
//ref ColliderAabb collider = ref EntityNativeDBExtensions.QueryEntity<ColliderAabb>(entitiesDB, (uint) groupID, BlockGroupExclusiveGroups.BlockGroupEntityGroup);
//float3 bottomOffset = PlaceBlockUtility.GetBottomOffset(collider);
//var rootPosition = math.mul(groupTransform.blockGroupGridRotation, bottomOffset) + groupTransform.blockGroupGridPosition;
//var rootRotation = groupTransform.blockGroupGridRotation;
clipboardManager.SetGhostSerialized(blueprintID, false);
SelectionSerializationUtility.CopySelectionToClipboard(playerID, entitiesDB,
serializationData.blueprintData, entitySerialization, entityFactory, blockIDs,
(uint) blockIDs.Length, pos, rot, -1);
BuildGhostBlueprint(selected, pos, rot, playerID);
SerializeGhostBlueprint.Invoke(SerializeGhostBlueprintInstance, new object[] {playerID, blueprintID});
}
private void BuildGhostBlueprint(ICollection<Block> blocks, float3 pos, quaternion rot, uint playerID)
{
GhostChildUtility.ClearGhostChildren(playerID, entitiesDB, entityFunctions);
var bssesopt = entitiesDB.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(playerID,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup));
if (!bssesopt)
return;
foreach (var block in blocks)
{
GhostChildUtility.BuildGhostChild(in playerID, block.Id, in pos, in rot, entitiesDB,
BuildGhostBlueprintFactory, false, bssesopt.Get().buildingDroneReference,
FullGameFields._managers.blockLabelResourceManager);
}
}
public Block[] PlaceBlueprintBlocks(uint blueprintID, uint playerID, float3 pos, float3 rot)
{ //RobocraftX.CR.MachineEditing.PlaceBlueprintUtility.PlaceBlocksFromSerialisedData
var serializationData = clipboardManager.GetSerializationData(blueprintID);
var blueprintData = serializationData.blueprintData;
blueprintData.dataPos = 0U;
uint selectionSize;
PositionEntityStruct selectionPosition;
RotationEntityStruct selectionRotation;
uint version;
BoxSelectSerializationUtilities.ReadClipboardHeader(blueprintData, out selectionSize, out selectionPosition, out selectionRotation, out version);
((FasterList<EGID>) LocalBlockMap.GetValue(null)).Clear();
if (version <= 1U)
{
uint groupsCount;
BoxSelectSerializationUtilities.ReadBlockGroupData(blueprintData, out groupsCount);
for (int index = 0; (long) index < (long) groupsCount; ++index)
{
int nextFilterId = BlockGroupUtility.NextFilterId;
entitySerialization.DeserializeNewEntity(new EGID((uint) nextFilterId, BlockGroupExclusiveGroups.BlockGroupEntityGroup), blueprintData, 1);
}
}
int nextFilterId1 = BlockGroupUtility.NextFilterId;
entityFactory.BuildEntity<BlockGroupEntityDescriptor>(new EGID((uint) nextFilterId1,
BlockGroupExclusiveGroups.BlockGroupEntityGroup)).Init(new BlockGroupTransformEntityComponent
{
blockGroupGridPosition = selectionPosition.position,
blockGroupGridRotation = selectionRotation.rotation
});
var frot = Quaternion.Euler(rot);
var grid = new GridRotationStruct {position = pos, rotation = frot};
var poss = new PositionEntityStruct {position = pos};
var rots = new RotationEntityStruct {rotation = frot};
for (int index = 0; (long) index < (long) selectionSize; ++index)
BuildBlock.Invoke(null,
new object[]
{
playerID, grid, poss, rots, selectionPosition, selectionRotation, blueprintData,
entitySerialization, nextFilterId1
});
/*
uint playerId, in GridRotationStruct ghostParentGrid,
in PositionEntityStruct ghostParentPosition, in RotationEntityStruct ghostParentRotation,
in PositionEntityStruct selectionPosition, in RotationEntityStruct selectionRotation,
ISerializationData serializationData, EntitiesDB entitiesDb,
IEntitySerialization entitySerialization, int blockGroupId
*/
if (globalBlockMap == null)
globalBlockMap = FullGameFields._deserialisedBlockMap;
var placedBlocks = (FasterList<EGID>) LocalBlockMap.GetValue(null);
globalBlockMap.Clear();
globalBlockMap.AddRange(placedBlocks);
BuildWires.Invoke(null,
new object[] {playerID, blueprintData, entitySerialization, entitiesDB, entityFactory});
var blocks = new Block[placedBlocks.count];
for (int i = 0; i < blocks.Length; i++)
blocks[i] = Block.New(placedBlocks[i]);
return blocks;
}
public void GetBlueprintInfo(uint blueprintID, out float3 pos, out quaternion rot, out uint selectionSize)
{
var serializationData = clipboardManager.GetSerializationData(blueprintID);
var blueprintData = serializationData.blueprintData;
blueprintData.dataPos = 0U;
BoxSelectSerializationUtilities.ReadClipboardHeader(blueprintData, out selectionSize, out var posst,
out var rotst, out _);
blueprintData.dataPos = 0U; //Just to be sure, it gets reset when it's read anyway
pos = posst.position;
rot = rotst.rotation;
}
public void InitBlueprint(uint blueprintID)
{
clipboardManager.IncrementRefCount(blueprintID);
}
public void DisposeBlueprint(uint 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 bool isRemovable { get; } = false;
[HarmonyPatch]
private static class RemoveEnginePatch
{
public static void Prefix(IEntityFunctions entityFunctions,
MachineGraphConnectionEntityFactory machineGraphConnectionEntityFactory)
{
nativeBlockRemove = entityFunctions.ToNativeRemove<BlockEntityDescriptor>("TBAPI" + nameof(BlueprintEngine));
nativeConnectionRemove = entityFunctions.ToNativeRemove<MachineConnectionEntityDescriptor>("TBAPI" + nameof(BlueprintEngine));
connectionFactory = machineGraphConnectionEntityFactory;
BlueprintEngine.entityFunctions = entityFunctions;
}
public static MethodBase TargetMethod()
{
return AccessTools.GetDeclaredConstructors(AccessTools.TypeByName("RobocraftX.CR.MachineEditing.RemoveBlockEngine"))[0];
}
}
[HarmonyPatch]
private static class SelectEnginePatch
{
public static void Prefix(ClipboardSerializationDataResourceManager clipboardSerializationDataResourceManager,
IEntitySerialization entitySerialization,
IEntityFactory entityFactory)
{
clipboardManager = clipboardSerializationDataResourceManager;
BlueprintEngine.entitySerialization = entitySerialization;
BlueprintEngine.entityFactory = entityFactory;
}
public static MethodBase TargetMethod()
{
return AccessTools.GetDeclaredConstructors(AccessTools.TypeByName("RobocraftX.CR.MachineEditing.SelectBlockEngine"))[0];
}
}
[HarmonyPatch]
private static class SerializeGhostBlueprintPatch
{
public static void Postfix(object __instance)
{
SerializeGhostBlueprintInstance = __instance;
}
public static MethodBase TargetMethod()
{
return AccessTools.GetDeclaredConstructors(SerializeGhostBlueprintType)[0];
}
}
[HarmonyPatch]
private static class BuildGhostBlueprintPatch
{
public static void Postfix(GhostChildEntityFactory ghostChildEntityFactory)
{
BuildGhostBlueprintFactory = ghostChildEntityFactory;
}
public static MethodBase TargetMethod()
{
return AccessTools.GetDeclaredConstructors(AccessTools.TypeByName("RobocraftX.CR.MachineEditing.BuildGhostChildForMultiblockPickEngine"))[0];
}
}
public IEntityFactory Factory { get; set; }
}
}

View file

@ -0,0 +1,69 @@
using RobocraftX.Common;
using RobocraftX.DOTS;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Mathematics;
using Unity.Transforms;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines
{
/// <summary>
/// Engine which executes block movement actions
/// </summary>
public class MovementEngine : IApiEngine
{
public string Name { get; } = "TechbloxModdingAPIMovementGameEngine";
public EntitiesDB entitiesDB { set; private get; }
public bool isRemovable => false;
public bool IsInGame = false;
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
// implementations for Movement static class
internal float3 MoveBlock(Block block, float3 vector)
{
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntityOrDefault<PositionEntityStruct>(block);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(block);
var phyStruct = this.entitiesDB.QueryEntityOptional<DOTSPhysicsEntityStruct>(block);
// main (persistent) position
posStruct.position = vector;
// placement grid position
gridStruct.position = vector;
// rendered position
transStruct.position = vector;
// collision position
if (phyStruct)
{ //It exists
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.Get().dotsEntity, new Translation
{
Value = posStruct.position
});
}
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).areConnectionsAssigned = false;
return posStruct.position;
}
internal float3 GetPosition(Block block)
{
return entitiesDB.QueryEntityOrDefault<PositionEntityStruct>(block).position;
}
}
}

View file

@ -0,0 +1,131 @@
using System.Reflection;
using DataLoader;
using Gamecraft.Blocks.BlockGroups;
using Gamecraft.Wires;
using HarmonyLib;
using RobocraftX.Blocks;
using RobocraftX.Character;
using RobocraftX.Common;
using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.Rendering;
using RobocraftX.Rendering.GPUI;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Mathematics;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines
{
/// <summary>
/// Engine which executes block placement actions
/// </summary>
public class PlacementEngine : IApiEngine
{
public bool IsInGame;
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
public EntitiesDB entitiesDB { get; set; }
private static BlockEntityFactory _blockEntityFactory; //Injected from PlaceSingleBlockEngine
private static IEntityFactory _entityFactory;
public EntityInitializer PlaceBlock(BlockIDs block, float3 position, Player player, bool autoWire)
{ //It appears that only the non-uniform scale has any visible effect, but if that's not given here it will be set to the uniform one
return BuildBlock((ushort) block, position, autoWire, (player ?? Player.LocalPlayer).Id);
}
private EntityInitializer BuildBlock(ushort block, float3 position, bool autoWire, uint playerId)
{
if (_blockEntityFactory == null)
throw new BlockException("The factory is null.");
if(!FullGameFields._dataDb.ContainsKey<CubeListData>(block))
throw new BlockException("Block with ID " + block + " not found!");
//RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine
DBEntityStruct dbEntity = new DBEntityStruct {DBID = block};
EntityInitializer structInitializer = _blockEntityFactory.Build(CommonExclusiveGroups.blockIDGeneratorClient.Next(), block); //The ghost block index is only used for triggers
uint prefabAssetID = structInitializer.Has<PrefabAssetIDComponent>()
? structInitializer.Get<PrefabAssetIDComponent>().prefabAssetID
: throw new BlockException("Prefab asset ID not found!"); //Set by the game
uint prefabId = PrefabsID.GetOrAddPrefabID((ushort) prefabAssetID, (byte) BlockMaterial.SteelBodywork, 1, false);
structInitializer.Init(new GFXPrefabEntityStructGPUI(prefabId));
structInitializer.Init(dbEntity);
structInitializer.Init(new PositionEntityStruct {position = position});
structInitializer.Init(new RotationEntityStruct {rotation = quaternion.identity});
structInitializer.Init(new ScalingEntityStruct {scale = new float3(1, 1, 1)});
structInitializer.Init(new GridRotationStruct
{
position = position,
rotation = quaternion.identity
});
structInitializer.Init(new UniformBlockScaleEntityStruct {scaleFactor = 1});
structInitializer.Get<CubeMaterialStruct>().materialId = (byte) BlockMaterial.SteelBodywork;
var bssesopt = entitiesDB.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(playerId,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup));
if (!bssesopt)
throw new BlockException("Invalid player ID specified for block placement");
structInitializer.Init(new BlockPlacementInfoStruct
{
loadedFromDisk = false,
placedByBuildingDrone = bssesopt.Get().buildingDroneReference,
triggerAutoWiring = autoWire && structInitializer.Has<BlockPortsStruct>()
});
int nextFilterId = BlockGroupUtility.NextFilterId;
structInitializer.Init(new BlockGroupEntityComponent
{
currentBlockGroup = nextFilterId
});
_entityFactory.BuildEntity<BlockGroupEntityDescriptor>((uint) nextFilterId,
BlockGroupExclusiveGroups.BlockGroupEntityGroup)
.Init(new BlockGroupTransformEntityComponent
{
blockGroupGridRotation = quaternion.identity,
blockGroupGridPosition = position
});
foreach (var group in CharacterExclusiveGroups.AllCharacters)
{
EGID playerEGID = new EGID(playerId, group);
if (!entitiesDB.TryQueryEntitiesAndIndex<PickedBlockExtraDataStruct>(playerEGID, out uint index,
out var array)) continue;
ref PickedBlockExtraDataStruct pickedBlock = ref array[index];
pickedBlock.placedBlockEntityID = structInitializer.EGID;
pickedBlock.placedBlockWasAPickedBlock = false;
}
return structInitializer;
}
public string Name => "TechbloxModdingAPIPlacementGameEngine";
public bool isRemovable => false;
[HarmonyPatch]
class FactoryObtainerPatch
{
static void Postfix(BlockEntityFactory blockEntityFactory, IEntityFactory entityFactory)
{
_blockEntityFactory = blockEntityFactory;
_entityFactory = entityFactory;
Logging.MetaDebugLog("Block entity factory injected.");
}
static MethodBase TargetMethod(Harmony instance)
{
return AccessTools.TypeByName("RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine").GetConstructors()[0];
}
}
}
}

View file

@ -0,0 +1,86 @@
using System.Reflection;
using Gamecraft.Blocks.BlockGroups;
using HarmonyLib;
using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.GroupTags;
using RobocraftX.StateSync;
using Svelto.ECS;
using Svelto.ECS.Native;
using Techblox.Blocks.Connections;
using Unity.Collections;
using Unity.Jobs;
using Allocator = Unity.Collections.Allocator;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks.Engines
{
public class RemovalEngine : IApiEngine, IDeterministicTimeStopped
{
private static IEntityFunctions _entityFunctions;
private static MachineGraphConnectionEntityFactory _connectionFactory;
private NativeHashSet<ulong> removedConnections;
public bool RemoveBlock(EGID target)
{
if (!entitiesDB.Exists<MachineGraphConnectionsEntityStruct>(target))
return false;
using var connStructMapper =
entitiesDB.QueryNativeMappedEntities<MachineGraphConnectionsEntityStruct>(GroupTag<BLOCK_TAG>.Groups,
Svelto.Common.Allocator.Temp);
if (entitiesDB.TryQueryNativeMappedEntities<MachineConnectionComponent>(
ConnectionsExclusiveGroups.MACHINE_CONNECTION_GROUP, out var mapper))
{
BlockGroupUtility.RemoveBlockConnections(target, removedConnections, _connectionFactory,
connStructMapper, mapper, entitiesDB.GetEntityReferenceMap(), _entityFunctions);
}
_entityFunctions.RemoveEntity<BlockEntityDescriptor>(target);
return true;
}
public void Ready()
{
removedConnections = new(2000, Allocator.Persistent);
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
removedConnections.Dispose();
}
public string Name => "TechbloxModdingAPIRemovalGameEngine";
public string name => Name;
public bool isRemovable => false;
[HarmonyPatch]
class FactoryObtainerPatch
{
static void Postfix(IEntityFunctions entityFunctions,
MachineGraphConnectionEntityFactory machineGraphConnectionEntityFactory)
{
_entityFunctions = entityFunctions;
_connectionFactory = machineGraphConnectionEntityFactory;
Logging.MetaDebugLog("Requirements injected.");
}
static MethodBase TargetMethod(Harmony instance)
{
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

@ -0,0 +1,76 @@
using RobocraftX.Common;
using RobocraftX.DOTS;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Mathematics;
using UnityEngine;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines
{
/// <summary>
/// Engine which executes block movement actions
/// </summary>
public class RotationEngine : IApiEngine
{
public string Name { get; } = "TechbloxModdingAPIRotationGameEngine";
public EntitiesDB entitiesDB { set; private get; }
public bool isRemovable => false;
public bool IsInGame = false;
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
// implementations for Rotation static class
internal float3 RotateBlock(Block block, Vector3 vector)
{
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(block);
var phyStruct = this.entitiesDB.QueryEntityOptional<DOTSPhysicsEntityStruct>(block);
// main (persistent) rotation
Quaternion newRotation = rotStruct.rotation;
newRotation.eulerAngles = vector;
rotStruct.rotation = newRotation;
// placement grid rotation
gridStruct.rotation = newRotation;
// rendered rotation
transStruct.rotation = newRotation;
// collision rotation
if (phyStruct)
{ //It exists
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.Get().dotsEntity,
new Unity.Transforms.Rotation
{
Value = rotStruct.rotation
});
}
// 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;
}
internal float3 GetRotation(Block block)
{
ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block);
return ((Quaternion) rotStruct.rotation).eulerAngles;
}
}
}

View file

@ -0,0 +1,59 @@
using System.Reflection;
using HarmonyLib;
using RobocraftX.Common;
using RobocraftX.DOTS;
using Svelto.ECS;
using Unity.Entities;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks.Engines
{
public class ScalingEngine : IApiEngine
{
private static IReactOnAddAndRemove<DOTSPhysicsEntityCreationStruct> physicsEngine;
public void Ready()
{
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
}
public string Name { get; } = "TechbloxModdingAPIScalingEngine";
public bool isRemovable { get; } = false;
private EntityManager _entityManager; //Unity entity manager
public void UpdateCollision(EGID egid)
{
if (_entityManager == default)
_entityManager = FullGameFields._physicsWorld.EntityManager;
//Assuming the block exists
var entity = entitiesDB.QueryEntity<DOTSPhysicsEntityStruct>(egid).dotsEntity;
var pes = new DOTSPhysicsEntityCreationStruct();
physicsEngine.Add(ref pes, egid); //Create new DOTS entity
_entityManager.DestroyEntity(entity);
}
[HarmonyPatch]
class PhysicsEnginePatch
{
static void Postfix(IReactOnAddAndRemove<DOTSPhysicsEntityCreationStruct> __instance)
{
physicsEngine = __instance;
Logging.MetaDebugLog("Physics engine injected.");
}
static MethodBase TargetMethod(Harmony instance)
{
return AccessTools.Method("RobocraftX.StateSync.HandleDOTSPhysicEntitiesWithPrefabCreationEngine" +
":Ready");
}
}
}
}

View file

@ -0,0 +1,357 @@
using System;
using Gamecraft.Wires;
using Svelto.DataStructures;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines
{
/// <summary>
/// Engine which executes signal actions
/// </summary>
public class SignalEngine : IApiEngine, IFactoryEngine
{
public const float POSITIVE_HIGH = 1.0f;
public const float NEGATIVE_HIGH = -1.0f;
public const float HIGH = 1.0f;
public const float ZERO = 0.0f;
public string Name { get; } = "TechbloxModdingAPISignalGameEngine";
public EntitiesDB entitiesDB { set; private get; }
public IEntityFactory Factory { get; set; }
public bool isRemovable => false;
public bool IsInGame = false;
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
// implementations for block wiring
public (WireEntityStruct Wire, EGID ID) CreateNewWire(EGID startBlock, byte startPort, EGID endBlock, byte endPort)
{
EGID wireEGID = new EGID(BuildModeWiresGroups.NewWireEntityId, BuildModeWiresGroups.WiresGroup.Group);
EntityInitializer wireInitializer = Factory.BuildEntity<WireEntityDescriptor>(wireEGID);
wireInitializer.Init(new WireEntityStruct
{
sourceBlockEGID = startBlock,
sourcePortUsage = startPort,
destinationBlockEGID = endBlock,
destinationPortUsage = endPort
});
return (wireInitializer.Get<WireEntityStruct>(), wireEGID);
}
public ref WireEntityStruct GetWire(EGID wire)
{
if (!entitiesDB.Exists<WireEntityStruct>(wire))
{
throw new WiringException($"Wire {wire} does not exist");
}
return ref entitiesDB.QueryEntity<WireEntityStruct>(wire);
}
public ref PortEntityStruct GetPort(EGID port)
{
if (!entitiesDB.Exists<PortEntityStruct>(port))
{
throw new WiringException($"Port {port} does not exist (yet?)");
}
return ref entitiesDB.QueryEntity<PortEntityStruct>(port);
}
public ref PortEntityStruct GetPortByOffset(BlockPortsStruct bps, byte portNumber, bool input)
{
ExclusiveGroup group = input
? NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group
: NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group;
uint id = (input ? bps.firstInputID : bps.firstOutputID) + portNumber;
EGID egid = new EGID(id, group);
if (!entitiesDB.Exists<PortEntityStruct>(egid))
{
throw new WiringException("Port does not exist");
}
return ref entitiesDB.QueryEntity<PortEntityStruct>(egid);
}
public ref PortEntityStruct GetPortByOffset(Block block, byte portNumber, bool input)
{
var bps = entitiesDB.QueryEntityOptional<BlockPortsStruct>(block);
if (!bps)
{
throw new BlockException("Block does not exist");
}
return ref GetPortByOffset(bps, portNumber, input);
}
public ref T GetComponent<T>(EGID egid) where T : unmanaged, IEntityComponent
{
return ref entitiesDB.QueryEntity<T>(egid);
}
public bool Exists<T>(EGID egid) where T : struct, IEntityComponent
{
return entitiesDB.Exists<T>(egid);
}
public bool SetSignal(EGID blockID, float signal, out uint signalID, bool input = true)
{
signalID = GetSignalIDs(blockID, input)[0];
return SetSignal(signalID, signal);
}
public bool SetSignal(uint signalID, float signal, bool input = true)
{
var (array, count) = GetSignalStruct(signalID, out uint index, input);
if (count > 0) array[index].valueAsFloat = signal;
return false;
}
public float AddSignal(EGID blockID, float signal, out uint signalID, bool clamp = true, bool input = true)
{
signalID = GetSignalIDs(blockID, input)[0];
return AddSignal(signalID, signal, clamp, input);
}
public float AddSignal(uint signalID, float signal, bool clamp = true, bool input = true)
{
var (array, count) = GetSignalStruct(signalID, out uint index, input);
if (count > 0)
{
ref var channelData = ref array[index];
channelData.valueAsFloat += signal;
if (clamp)
{
if (channelData.valueAsFloat > POSITIVE_HIGH)
{
channelData.valueAsFloat = POSITIVE_HIGH;
}
else if (channelData.valueAsFloat < NEGATIVE_HIGH)
{
channelData.valueAsFloat = NEGATIVE_HIGH;
}
return channelData.valueAsFloat;
}
}
return signal;
}
public float GetSignal(EGID blockID, out uint signalID, bool input = true)
{
signalID = GetSignalIDs(blockID, input)[0];
return GetSignal(signalID, input);
}
public float GetSignal(uint signalID, bool input = true)
{
var (array, count) = GetSignalStruct(signalID, out uint index, input);
return count > 0 ? array[index].valueAsFloat : 0f;
}
public uint[] GetSignalIDs(EGID blockID, bool input = true)
{
ref BlockPortsStruct bps = ref entitiesDB.QueryEntity<BlockPortsStruct>(blockID);
uint[] signals;
if (input) {
signals = new uint[bps.inputCount];
for (uint i = 0u; i < bps.inputCount; i++)
{
signals[i] = bps.firstInputID + i;
}
} else {
signals = new uint[bps.outputCount];
for (uint i = 0u; i < bps.outputCount; i++)
{
signals[i] = bps.firstOutputID + i;
}
}
return signals;
}
public EGID[] GetSignalInputs(EGID blockID)
{
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(blockID);
EGID[] inputs = new EGID[ports.inputCount];
for (uint i = 0; i < ports.inputCount; i++)
{
inputs[i] = new EGID(i + ports.firstInputID, NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group);
}
return inputs;
}
public EGID[] GetSignalOutputs(EGID blockID)
{
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(blockID);
EGID[] outputs = new EGID[ports.outputCount];
for (uint i = 0; i < ports.outputCount; i++)
{
outputs[i] = new EGID(i + ports.firstOutputID, NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group);
}
return outputs;
}
public OptionalRef<PortEntityStruct> MatchBlockIOToPort(Block block, byte portUsage, bool output)
{
return MatchBlockIOToPort(block.Id, portUsage, output);
}
public OptionalRef<PortEntityStruct> MatchBlockIOToPort(EGID block, byte portUsage, bool output)
{
if (!entitiesDB.Exists<BlockPortsStruct>(block))
return default;
var group = output
? NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group
: NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group;
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
if (!entitiesDB.TryQueryMappedEntities<PortEntityStruct>(group, out var mapper))
return default;
for (uint i = 0; i < (output ? ports.outputCount : ports.inputCount); ++i)
{
uint entityID = (output ? ports.firstOutputID : ports.firstInputID) + i;
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;
}
public OptionalRef<WireEntityStruct> MatchPortToWire(PortEntityStruct port, EGID blockID, out EGID wireID)
{
var (wires, ids, count) = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group);
for (uint i = 0; i < count; i++)
{
if ((wires[i].destinationPortUsage == port.usage && wires[i].destinationBlockEGID == blockID)
|| (wires[i].sourcePortUsage == port.usage && wires[i].sourceBlockEGID == blockID))
{
wireID = new EGID(ids[i], BuildModeWiresGroups.WiresGroup.Group);
return new OptionalRef<WireEntityStruct>(wires, i);
}
}
wireID = default;
return default;
}
public EGID MatchBlocksToWire(EGID startBlock, EGID endBlock, byte startPort = byte.MaxValue, byte endPort = byte.MaxValue)
{
EGID[] startPorts;
if (startPort == byte.MaxValue)
{
// search all output ports on source block
startPorts = GetSignalOutputs(startBlock);
}
else
{
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(startBlock);
startPorts = new EGID[] {new EGID(ports.firstOutputID + startPort, NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group) };
}
EGID[] endPorts;
if (startPort == byte.MaxValue)
{
// search all input ports on destination block
endPorts = GetSignalInputs(endBlock);
}
else
{
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(endBlock);
endPorts = new EGID[] {new EGID(ports.firstInputID + endPort, NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group) };
}
for (int endIndex = 0; endIndex < endPorts.Length; endIndex++)
{
PortEntityStruct endPES = entitiesDB.QueryEntity<PortEntityStruct>(endPorts[endIndex]);
for (int startIndex = 0; startIndex < startPorts.Length; startIndex++)
{
PortEntityStruct startPES = entitiesDB.QueryEntity<PortEntityStruct>(startPorts[startIndex]);
foreach (var wireOpt in entitiesDB.QueryEntitiesOptional<WireEntityStruct>(
NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group))
{
var wire = wireOpt.Get();
if ((wire.destinationPortUsage == endPES.usage && wire.destinationBlockEGID == endBlock)
&& (wire.sourcePortUsage == startPES.usage && wire.sourceBlockEGID == startBlock))
{
return wireOpt.EGID;
}
}
}
}
return default;
}
public OptionalRef<ChannelDataStruct> GetChannelDataStruct(EGID portID)
{
var port = GetPort(portID);
var (channels, count) = entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<BuildModeWiresGroups.ChannelDataGroup>.Group);
return port.firstChannelIndexCachedInSim < count
? new OptionalRef<ChannelDataStruct>(channels, port.firstChannelIndexCachedInSim)
: default;
}
public EGID[] GetElectricBlocks()
{
var res = new FasterList<EGID>();
foreach (var ((coll, ids, count), _) in entitiesDB.QueryEntities<BlockPortsStruct>())
{
for (int i = 0; i < count; i++)
{
ref BlockPortsStruct s = ref coll[i];
//res.Add(s.ID); - TODO: Would need to search for the groups for each block
}
}
return res.ToArray();
}
public EGID[] WiredToInput(EGID block, byte port)
{
return entitiesDB
.QueryEntitiesOptional<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group)
.ToArray(wire => wire.ID,
wire => wire.Component.destinationPortUsage == port && wire.Component.destinationBlockEGID == block);
}
public EGID[] WiredToOutput(EGID block, byte port)
{
return entitiesDB
.QueryEntitiesOptional<WireEntityStruct>(NamedExclusiveGroup<BuildModeWiresGroups.WiresGroup>.Group)
.ToArray(wire => wire.ID,
wire => wire.Component.sourcePortUsage == port && wire.Component.sourceBlockEGID == block);
}
private EntityCollection<ChannelDataStruct> GetSignalStruct(uint signalID, out uint index, bool input = true)
{
ExclusiveGroup group = input
? NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group
: NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group;
if (entitiesDB.Exists<PortEntityStruct>(signalID, group))
{
index = entitiesDB.QueryEntity<PortEntityStruct>(signalID, group).firstChannelIndexCachedInSim;
var channelData =
entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<BuildModeWiresGroups.ChannelDataGroup>.Group);
return channelData;
}
index = 0;
return default; //count: 0
}
}
}

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

@ -0,0 +1,165 @@
using System;
using Gamecraft.Wires;
using Svelto.ECS;
using Unity.Mathematics;
using TechbloxModdingAPI;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks
{
/// <summary>
/// Common implementation for blocks that support wiring.
/// </summary>
public class SignalingBlock : Block
{
public SignalingBlock(EGID id) : base(id)
{
}
public SignalingBlock(uint id) : base(id)
{
}
/// <summary>
/// Generates the input port identifiers.
/// </summary>
/// <returns>The input identifiers.</returns>
protected EGID[] GetInputIds()
{
return SignalEngine.GetSignalInputs(Id);
}
/// <summary>
/// Generates the output port identifiers.
/// </summary>
/// <returns>The output identifiers.</returns>
protected EGID[] GetOutputIds()
{
return SignalEngine.GetSignalOutputs(Id);
}
/// <summary>
/// Gets the connected wire.
/// </summary>
/// <returns>The connected wire.</returns>
/// <param name="portId">Port identifier.</param>
/// <param name="connected">Whether the port has a wire connected to it.</param>
protected OptionalRef<WireEntityStruct> GetConnectedWire(PortEntityStruct port, out EGID egid)
{
return SignalEngine.MatchPortToWire(port, Id, out egid);
}
/// <summary>
/// [EXPERIMENTAL] Gets the channel data.
/// </summary>
/// <returns>The channel data.</returns>
/// <param name="portId">Port identifier.</param>
protected OptionalRef<ChannelDataStruct> GetChannelData(EGID portId)
{
return SignalEngine.GetChannelDataStruct(portId);
}
/// <summary>
/// The input port count.
/// </summary>
public uint InputCount
{
get => BlockEngine.GetBlockInfo<BlockPortsStruct>(this).inputCount;
}
/// <summary>
/// The output port count.
/// </summary>
public uint OutputCount
{
get => BlockEngine.GetBlockInfo<BlockPortsStruct>(this).outputCount;
}
/// <summary>
/// Connect an output on this block to an input on another block.
/// </summary>
/// <param name="sourcePort">Output port number.</param>
/// <param name="destination">Input block.</param>
/// <param name="destinationPort">Input port number.</param>
/// <returns>The wire connection</returns>
/// <exception cref="WiringException">The wire could not be created.</exception>
public Wire Connect(byte sourcePort, SignalingBlock destination, byte destinationPort)
{
if (sourcePort >= OutputCount)
{
throw new WiringException("Source port does not exist");
}
if (destinationPort >= destination.InputCount)
{
throw new WiringException("Destination port does not exist");
}
return Wire.Connect(this, sourcePort, destination, destinationPort);
}
/// <summary>
/// The port's name.
/// This is localized to the user's language, so this is not reliable for port identification.
/// </summary>
/// <param name="port">Port number.</param>
/// <param name="input">Whether the port is an input (true) or an output (false).</param>
/// <returns>The localized port name.</returns>
public string PortName(byte port, bool input)
{
PortEntityStruct pes = SignalEngine.GetPortByOffset(this, port, input);
return pes.portNameLocalised;
}
/// <summary>
/// The input port's name.
/// </summary>
/// <param name="port">Input port number.</param>
/// <returns>The port name, localized to the user's language.</returns>
public string InputPortName(byte port) => PortName(port, true);
/// <summary>
/// The output port's name.
/// </summary>
/// <param name="port">Output port number.</param>
/// <returns>The port name, localized to the user's language.</returns>
public string OutputPortName(byte port) => PortName(port, false);
/// <summary>
/// All wires connected to the input port.
/// These wires will always be wired output -> input.
/// </summary>
/// <param name="port">Port number.</param>
/// <returns>Wires connected to the input port.</returns>
public Wire[] ConnectedToInput(byte port)
{
if (port >= InputCount) throw new WiringException($"Port input {port} does not exist");
EGID[] wireEgids = SignalEngine.WiredToInput(Id, port);
Wire[] wires = new Wire[wireEgids.Length];
for (uint i = 0; i < wireEgids.Length; i++)
{
wires[i] = new Wire(wireEgids[i]);
}
return wires;
}
/// <summary>
/// All wires connected to the output port.
/// These wires will always be wired output -> input.
/// </summary>
/// <param name="port">Port number.</param>
/// <returns>Wires connected to the output port.</returns>
public Wire[] ConnectedToOutput(byte port)
{
if (port >= OutputCount) throw new WiringException($"Port output {port} does not exist");
EGID[] wireEgids = SignalEngine.WiredToOutput(Id, port);
Wire[] wires = new Wire[wireEgids.Length];
for (uint i = 0; i < wireEgids.Length; i++)
{
wires[i] = new Wire(wireEgids[i]);
}
return wires;
}
}
}

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

@ -0,0 +1,313 @@
using System;
using Gamecraft.Wires;
using Svelto.ECS;
using Svelto.ECS.Experimental;
using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks
{
public class Wire : EcsObjectBase
{
internal static SignalEngine signalEngine;
protected EGID startPortEGID;
protected EGID endPortEGID;
protected EGID startBlockEGID;
protected EGID endBlockEGID;
protected EGID wireEGID;
protected bool inputToOutput;
protected byte startPort;
protected byte endPort;
public static Wire Connect(SignalingBlock start, byte startPort, SignalingBlock end, byte endPort)
{
var (wire, id) = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort);
return new Wire(wire, start, end, id);
}
/// <summary>
/// An existing wire connection ending at the specified input.
/// If multiple exist, this will return the first one found.
/// </summary>
/// <param name="end">Destination block.</param>
/// <param name="endPort">Port number.</param>
/// <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)
{
var port = signalEngine.MatchBlockIOToPort(end, endPort, false);
if (!port) return null;
var wire = signalEngine.MatchPortToWire(port, end.Id, out var egid);
return wire
? new Wire(wire.Get().sourceBlockEGID, end.Id, wire.Get().sourcePortUsage, endPort, egid, false)
: null;
}
/// <summary>
/// An existing wire connection starting at the specified output.
/// If multiple exist, this will return the first one found.
/// </summary>
/// <param name="start">Source block entity ID.</param>
/// <param name="startPort">Port number.</param>
/// <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)
{
var port = signalEngine.MatchBlockIOToPort(start, startPort, true);
if (!port) return null;
var wire = signalEngine.MatchPortToWire(port, start.Id, out var egid);
return wire
? new Wire(start.Id, wire.Get().destinationBlockEGID, startPort, wire.Get().destinationPortUsage, egid, false)
: null;
}
/// <summary>
/// Construct a wire object froam n existing connection.
/// </summary>
/// <param name="start">Starting block ID.</param>
/// <param name="end">Ending block ID.</param>
/// <param name="startPort">Starting 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>
public Wire(Block start, Block end, byte startPort = Byte.MaxValue, byte endPort = Byte.MaxValue) : base(ecs =>
{
var th = (Wire)ecs;
th.startBlockEGID = start.Id;
th.endBlockEGID = end.Id;
bool flipped = false;
// find block ports
EGID wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, startPort, endPort);
if (wire == default)
{
// flip I/O around and try again
wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, endPort, startPort);
flipped = true;
// 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
}
if (wire != default)
{
th.Construct(start.Id, end.Id, startPort, endPort, wire, flipped);
}
else
{
throw new WireInvalidException("Wire not found");
}
return th.wireEGID;
})
{
}
/// <summary>
/// Construct a wire object from an existing wire connection.
/// </summary>
/// <param name="start">Starting block ID.</param>
/// <param name="end">Ending block ID.</param>
/// <param name="startPort">Starting port number.</param>
/// <param name="endPort">Ending port number.</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>
public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput)
: this(start.Id, end.Id, startPort, endPort, wire, inputToOutput)
{
}
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.wireEGID = wire;
endPortEGID = signalEngine.MatchBlockIOToPort(startBlock, startPort, inputToOutput).EGID;
if (endPortEGID == default) throw new WireInvalidException("Wire end port not found");
startPortEGID = signalEngine.MatchBlockIOToPort(endBlock, endPort, !inputToOutput).EGID;
if (startPortEGID == default) throw new WireInvalidException("Wire start port not found");
this.startPort = startPort;
this.endPort = endPort;
}
/// <summary>
/// Construct a wire object from an existing wire connection.
/// </summary>
/// <param name="wireEgid">The wire ID.</param>
public Wire(EGID wireEgid) : base(wireEgid)
{
WireEntityStruct wire = signalEngine.GetWire(wireEGID);
Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage,
wireEgid, false);
}
private Wire(WireEntityStruct wire, SignalingBlock src, SignalingBlock dest, EGID wireEgid)
: this(src, dest, wire.sourcePortUsage, wire.destinationPortUsage, wireEgid, false)
{
}
/// <summary>
/// The wire's signal value, as a float.
/// </summary>
public float Float
{
get
{
return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsFloat;
}
set
{
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsFloat = value;
}
}
/// <summary>
/// The wire's string signal.
/// </summary>
public string String
{
get
{
return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString;
}
set
{
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString.Set(value);
}
}
/// <summary>
/// The wire's raw string signal.
/// </summary>
public ECSString ECSString
{
get
{
return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString;
}
set
{
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString = value;
}
}
/// <summary>
/// The wire's signal id.
/// I'm 50% sure this is useless.
/// </summary>
public uint SignalId
{
get
{
return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsID;
}
set
{
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsID = value;
}
}
/// <summary>
/// The block at the beginning of the wire.
/// </summary>
public SignalingBlock Start
{
get => (SignalingBlock)Block.New(startBlockEGID);
}
/// <summary>
/// The port number that the beginning of the wire connects to.
/// </summary>
public byte StartPort
{
get => startPort;
}
/// <summary>
/// The display name of the start port.
/// </summary>
public string StartPortName
{
get => signalEngine.GetPort(startPortEGID).portNameLocalised;
}
/// <summary>
/// The block at the end of the wire.
/// </summary>
public SignalingBlock End
{
get => (SignalingBlock)Block.New(endBlockEGID);
}
/// <summary>
/// The port number that the end of the wire connects to.
/// </summary>
public byte EndPort
{
get => endPort;
}
/// <summary>
/// The display name of the end port.
/// </summary>
public string EndPortName
{
get => signalEngine.GetPort(endPortEGID).portNameLocalised;
}
/// <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.
/// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input).
/// </summary>
/// <returns>A copy of the wire object.</returns>
public Wire OutputToInputCopy()
{
return GetInstance(wireEGID, egid => new Wire(egid));
}
/// <summary>
/// Convert the wire object to the direction the signal flows.
/// Signals on wires always flow from a block output port to a block input port.
/// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input).
/// </summary>
public void OutputToInputInPlace()
{
if (inputToOutput)
{
inputToOutput = false;
// swap inputs and outputs
(endBlockEGID, startBlockEGID) = (startBlockEGID, endBlockEGID);
var tempPort = endPortEGID;
endPortEGID = startPortEGID;
startPortEGID = tempPort;
(endPort, startPort) = (startPort, endPort);
}
}
public override string ToString()
{
if (signalEngine.Exists<WireEntityStruct>(wireEGID))
{
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} aka {(StartPort != byte.MaxValue ? Start.PortName(StartPort, inputToOutput) : "")}) -> ({End.Type}::{EndPort} aka {(EndPort != byte.MaxValue ? End.PortName(EndPort, !inputToOutput) : "")})";
}
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} -> {End.Type}::{EndPort})";
}
internal static void Init() { }
}
}

View file

@ -0,0 +1,102 @@
using System;
using Unity.Mathematics;
using UnityEngine;
namespace TechbloxModdingAPI
{
/// <summary>
/// Represents a blueprint in the inventory. When placed it becomes a block group.
/// </summary>
public class Blueprint : IDisposable
{
public uint Id { get; }
internal Blueprint(uint id)
{
Id = id;
BlockGroup._engine.InitBlueprint(id);
Refresh();
}
/// <summary>
/// The center of the blueprint. Can only be set using the StoreBlocks() method.
/// </summary>
public float3 Position { get; private set; }
/// <summary>
/// The rotation of the blueprint. Can only be set using the StoreBlocks() method.
/// </summary>
public float3 Rotation { get; private set; }
/// <summary>
/// The amount of blocks in the blueprint. Gan only be set using the StoreBlocks() method.
/// </summary>
public uint BlockCount { get; private set; }
/// <summary>
/// Creates a new, empty blueprint. It will be deleted on disposal unless the game holds a reference to it.
/// </summary>
/// <returns>A blueprint that doesn't have any blocks</returns>
public static Blueprint Create()
{
return new Blueprint(BlockGroup._engine.CreateBlueprint());
}
/// <summary>
/// Set the blocks that the blueprint contains.
/// Use the BlockGroup overload for automatically calculated position and rotation.
/// </summary>
/// <param name="blocks">The array of blocks to use</param>
/// <param name="position">The anchor (center) position of the blueprint</param>
/// <param name="rotation">The base rotation of the blueprint</param>
public void StoreBlocks(Block[] blocks, float3 position, float3 rotation)
{
BlockGroup._engine.ReplaceBlueprint(Player.LocalPlayer.Id, Id, blocks, position,
quaternion.Euler(rotation));
Refresh();
}
/// <summary>
/// Store the blocks from the given group in the blueprint with correct position and rotation for the blueprint.
/// </summary>
/// <param name="group">The block group to store</param>
public void StoreBlocks(BlockGroup group)
{
BlockGroup._engine.ReplaceBlueprint(Player.LocalPlayer.Id, Id, group, group.Position,
Quaternion.Euler(group.Rotation));
Refresh();
}
/// <summary>
/// Places the blocks the blueprint contains at the specified position and rotation.
/// </summary>
/// <param name="position">The position of the blueprint</param>
/// <param name="rotation">The rotation of the blueprint</param>
/// <returns>An array of the placed blocks</returns>
public Block[] PlaceBlocks(float3 position, float3 rotation)
{
return BlockGroup._engine.PlaceBlueprintBlocks(Id, Player.LocalPlayer.Id, position, rotation);
}
/// <summary>
/// Updates the properties based on the blueprint data. Only necessary if the blueprint is changed from the game.
/// </summary>
public void Refresh()
{
BlockGroup._engine.GetBlueprintInfo(Id, out var pos, out var rot, out uint count);
Position = pos;
Rotation = ((Quaternion) rot).eulerAngles;
BlockCount = count;
}
public void Dispose()
{
BlockGroup._engine.DisposeBlueprint(Id);
}
public override string ToString()
{
return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}, {nameof(BlockCount)}: {BlockCount}";
}
}
}

View file

@ -0,0 +1,75 @@
using Svelto.ECS;
using Techblox.TimeRunning.Clusters;
namespace TechbloxModdingAPI
{
/// <summary>
/// Represnts a cluster of blocks in time running mode, meaning blocks that are connected either directly or via joints.
/// Only exists if a cluster destruction manager is present. Static blocks like grass and dirt aren't part of a cluster.
/// </summary>
public class Cluster : EcsObjectBase
{
public Cluster(EGID id) : base(id)
{
}
public Cluster(uint id) : this(new EGID(id, ClustersExclusiveGroups.SIMULATION_CLUSTERS_GROUP))
{
}
public float InitialHealth //TODO
{
get => 0f;
set { }
}
public float CurrentHealth
{
get => 0f;
set { }
}
public float HealthMultiplier
{
get => 0f;
set { }
}
/// <summary>
/// The mass of the cluster.
/// </summary>
public float Mass => Block.BlockEngine.GetBlockInfo<ClusterMassComponent>(this).mass;
/// <summary>
/// Returns the simulation-time rigid bodies for the chunks in this cluster.
/// </summary>
/// <returns>An array of sim-bodies</returns>
public SimBody[] GetSimBodies()
{
return Block.BlockEngine.GetClusterBodies(Id.entityID);
}
public override string ToString()
{
return $"{nameof(Id)}: {Id}";
}
protected bool Equals(Cluster other)
{
return Id.Equals(other.Id);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Cluster) obj);
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}
}

View file

@ -0,0 +1,276 @@
using System;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Commands
{
/// <summary>
/// Custom Command builder.
/// </summary>
public class CommandBuilder
{
private string name;
private string description;
private short parameterCount;
private ICustomCommandEngine commandEngine;
private bool fromExisting = false;
/// <summary>
/// Create a new command builder.
/// </summary>
public CommandBuilder()
{
name = "";
description = null;
parameterCount = -1;
}
/// <summary>
/// Create and return a command builder.
/// </summary>
/// <returns>The builder.</returns>
public static CommandBuilder Builder()
{
return new CommandBuilder();
}
/// <summary>
/// Create a new command builder.
/// </summary>
/// <param name="name">The command name.</param>
/// <param name="description">The command description (shown in help).</param>
public CommandBuilder(string name, string description = null)
{
this.name = name;
this.description = description;
parameterCount = -1;
}
/// <summary>
/// Create and return a command builder.
/// If name and description are provided, this is equivalent to <code>Builder().Name(name).Description(description)</code>
/// </summary>
/// <param name="name">The command name.</param>
/// <param name="description">The command description (shown in help).</param>
/// <returns>The builder.</returns>
public static CommandBuilder Builder(string name, string description = null)
{
return new CommandBuilder(name, description);
}
/// <summary>
/// Name the command.
/// </summary>
/// <returns>The builder.</returns>
/// <param name="name">The command name.</param>
public CommandBuilder Name(string name)
{
this.name = name;
return this;
}
/// <summary>
/// Describe the command.
/// </summary>
/// <returns>The builder.</returns>
/// <param name="description">The command description (shown in help).</param>
public CommandBuilder Description(string description)
{
this.description = description;
return this;
}
/// <summary>
/// Set the action the command performs.
/// </summary>
/// <returns>The builder.</returns>
/// <param name="action">The action to perform when the command is called.</param>
public CommandBuilder Action(Action action)
{
parameterCount = 0;
commandEngine = new SimpleCustomCommandEngine(action, name, description);
return this;
}
/// <summary>
/// Set the action the command performs.
/// </summary>
/// <returns>The builder.</returns>
/// <param name="action">The action to perform when the command is called.</param>
/// <typeparam name="A">The 1st parameter's type.</typeparam>
public CommandBuilder Action<A>(Action<A> action)
{
parameterCount = 1;
commandEngine = new SimpleCustomCommandEngine<A>(action, name, description);
return this;
}
/// <summary>
/// Set the action the command performs.
/// </summary>
/// <returns>The builder.</returns>
/// <param name="action">The action to perform when the command is called.</param>
/// <typeparam name="A">The 1st parameter's type.</typeparam>
/// <typeparam name="B">The 2nd parameter's type.</typeparam>
public CommandBuilder Action<A,B>(Action<A,B> action)
{
parameterCount = 2;
commandEngine = new SimpleCustomCommandEngine<A,B>(action, name, description);
return this;
}
/// <summary>
/// Set the action the command performs.
/// </summary>
/// <returns>The builder.</returns>
/// <param name="action">The action to perform when the command is called.</param>
/// <typeparam name="A">The 1st parameter's type.</typeparam>
/// <typeparam name="B">The 2nd parameter's type.</typeparam>
/// <typeparam name="C">The 3rd parameter's type.</typeparam>
public CommandBuilder Action<A,B,C>(Action<A,B,C> action)
{
parameterCount = 3;
commandEngine = new SimpleCustomCommandEngine<A,B,C>(action, name, description);
return this;
}
/// <summary>
/// Build the command from an existing command.
/// </summary>
/// <returns>The command. Use Invoke() to execute it.</returns>
public SimpleCustomCommandEngine FromExisting()
{
if (string.IsNullOrWhiteSpace(name))
{
throw new CommandParameterMissingException("Command name must be defined before FromExisting() is called");
}
if (!ExistingCommands.Exists(name))
{
throw new CommandNotFoundException("Command cannot be built from existing because it does not exist.");
}
fromExisting = true;
return new SimpleCustomCommandEngine(
() => { ExistingCommands.Call(name); },
name,
description);
}
/// <summary>
/// Build the command from an existing command.
/// </summary>
/// <returns>The command. Use Invoke() to execute it.</returns>
/// <typeparam name="A">The 1st parameter's type.</typeparam>
public SimpleCustomCommandEngine<A> FromExisting<A>()
{
if (string.IsNullOrWhiteSpace(name))
{
throw new CommandParameterMissingException("Command name must be defined before FromExisting() is called");
}
if (!ExistingCommands.Exists(name))
{
throw new CommandNotFoundException("Command cannot be built from existing because it does not exist.");
}
fromExisting = true;
return new SimpleCustomCommandEngine<A>(
(A a) => { ExistingCommands.Call<A>(name, a); },
name,
description);
}
/// <summary>
/// Build the command from an existing command.
/// </summary>
/// <returns>The command. Use Invoke() to execute it.</returns>
/// <typeparam name="A">The 1st parameter's type.</typeparam>
/// <typeparam name="B">The 2nd parameter's type.</typeparam>
public SimpleCustomCommandEngine<A,B> FromExisting<A,B>()
{
if (string.IsNullOrWhiteSpace(name))
{
throw new CommandParameterMissingException("Command name must be defined before FromExisting() is called");
}
if (!ExistingCommands.Exists(name))
{
throw new CommandNotFoundException("Command cannot be built from existing because it does not exist.");
}
fromExisting = true;
return new SimpleCustomCommandEngine<A,B>(
(A a, B b) => { ExistingCommands.Call<A,B>(name, a, b); },
name,
description);
}
/// <summary>
/// Build the command from an existing command.
/// </summary>
/// <returns>The command. Use Invoke() to execute it.</returns>
/// <typeparam name="A">The 1st parameter's type.</typeparam>
/// <typeparam name="B">The 2nd parameter's type.</typeparam>
/// <typeparam name="C">The 3rd parameter's type.</typeparam>
public SimpleCustomCommandEngine<A,B,C> FromExisting<A,B,C>()
{
if (string.IsNullOrWhiteSpace(name))
{
throw new CommandParameterMissingException("Command name must be defined before FromExisting() is called");
}
if (!ExistingCommands.Exists(name))
{
throw new CommandNotFoundException("Command cannot be built from existing because it does not exist.");
}
fromExisting = true;
return new SimpleCustomCommandEngine<A,B,C>(
(A a, B b, C c) => { ExistingCommands.Call<A,B,C>(name, a, b, c); },
name,
description);
}
/// <summary>
/// Build the command.
/// </summary>
/// <returns>The built command.</returns>
/// <param name="register">Automatically register the command with CommandManager.AddCommand()?</param>
public ICustomCommandEngine Build(bool register = true)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new CommandParameterMissingException("Command name must be defined before Build() is called");
}
if (fromExisting)
{
throw new CommandAlreadyBuiltException("Command was already built by FromExisting()");
}
if (commandEngine == null)
{
throw new CommandParameterMissingException("Command action must be defined before Build() is called");
}
if (string.IsNullOrWhiteSpace(description))
{
Logging.LogWarning($"Command {FullName()} was built without a description");
}
if (register)
{
CommandManager.AddCommand(commandEngine);
Logging.MetaDebugLog($"Command {FullName()} was automatically registered");
}
return commandEngine;
}
/// <summary>
/// Get the full command name, in the form [name]::[description]::[# of parameters]
/// </summary>
/// <returns>The name.</returns>
public string FullName()
{
if (string.IsNullOrWhiteSpace(description))
{
return name + "::" + parameterCount;
}
return name + "::" + description + "::" + parameterCount;
}
}
}

View file

@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GamecraftModdingAPI.Commands
namespace TechbloxModdingAPI.Commands
{
/// <summary>
/// UNIMPLEMENTED!

View file

@ -0,0 +1,71 @@
using System;
namespace TechbloxModdingAPI.Commands
{
public class CommandException : TechbloxModdingAPIException
{
public CommandException() : base() {}
public CommandException(string msg) : base(msg) {}
public CommandException(string msg, Exception innerException) : base(msg, innerException) {}
}
public class CommandNotFoundException : CommandException
{
public CommandNotFoundException()
{
}
public CommandNotFoundException(string msg) : base(msg)
{
}
}
public class CommandAlreadyExistsException : CommandException
{
public CommandAlreadyExistsException()
{
}
public CommandAlreadyExistsException(string msg) : base(msg)
{
}
}
public class CommandRuntimeException : CommandException
{
public CommandRuntimeException()
{
}
public CommandRuntimeException(string msg) : base(msg)
{
}
public CommandRuntimeException(string msg, Exception innerException) : base(msg, innerException)
{
}
}
public class CommandAlreadyBuiltException : CommandException
{
public CommandAlreadyBuiltException()
{
}
public CommandAlreadyBuiltException(string msg) : base(msg)
{
}
}
public class CommandParameterMissingException : CommandException
{
public CommandParameterMissingException()
{
}
public CommandParameterMissingException(string msg) : base(msg)
{
}
}
}

View file

@ -5,10 +5,9 @@ using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Commands
namespace TechbloxModdingAPI.Commands
{
/// <summary>
/// Keeps track of custom commands
@ -22,6 +21,10 @@ namespace GamecraftModdingAPI.Commands
public static void AddCommand(ICustomCommandEngine engine)
{
if (ExistsCommand(engine))
{
throw new CommandAlreadyExistsException($"Command {engine.Name} already exists");
}
_customCommands[engine.Name] = engine;
if (_lastEngineRoot != null)
{

View file

@ -4,22 +4,17 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using uREPL;
using RobocraftX.CommandLine.Custom;
namespace GamecraftModdingAPI.Commands
namespace TechbloxModdingAPI.Commands
{
/// <summary>
/// Convenient methods for registering commands to Gamecraft.
/// Convenient methods for registering commands to Techblox.
/// All methods register to the command line and console block by default.
/// </summary>
public static class CommandRegistrationHelper
{
public static void Register(string name, Action action, string desc, bool noConsole = false)
{
RuntimeCommands.Register(name, action, desc);
if (noConsole) { return; }
ConsoleCommands.Register(name, action, desc);
CustomCommands.Register(name, action, desc);
}
public static void Register(string name, Action<object> action, string desc, bool noConsole = false)
@ -39,50 +34,42 @@ namespace GamecraftModdingAPI.Commands
public static void Register<Param0>(string name, Action<Param0> action, string desc, bool noConsole = false)
{
RuntimeCommands.Register<Param0>(name, action, desc);
if (noConsole) { return; }
ConsoleCommands.Register<Param0>(name, action, desc);
CustomCommands.Register(name, action, desc);
}
public static void Register<Param0, Param1>(string name, Action<Param0, Param1> action, string desc, bool noConsole = false)
{
RuntimeCommands.Register<Param0, Param1>(name, action, desc);
if (noConsole) { return; }
ConsoleCommands.Register<Param0, Param1>(name, action, desc);
CustomCommands.Register(name, action, desc);
}
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);
if (noConsole) { return; }
ConsoleCommands.Register<Param0, Param1, Param2>(name, action, desc);
CustomCommands.Register(name, action, desc);
}
public static void Unregister(string name, bool noConsole = false)
{
RuntimeCommands.Unregister(name);
if (noConsole) { return; }
ConsoleCommands.Unregister(name);
CustomCommands.Unregister(name);
}
public static void Call(string name)
{
RuntimeCommands.Call(name);
CustomCommands.Call(name);
}
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)
{
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)
{
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

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

View file

@ -6,12 +6,15 @@ using System.Threading.Tasks;
using Svelto.ECS;
using GamecraftModdingAPI.Utility;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Engines;
namespace GamecraftModdingAPI.Commands
namespace TechbloxModdingAPI.Commands
{
/// <summary>
/// Engine interface to handle command operations
/// Engine interface to handle command operations.
/// If you are using implementing this yourself, you must manually register the command.
/// See SimpleCustomCommandEngine's Ready() and Dispose() methods for an example of command registration.
/// </summary>
public interface ICustomCommandEngine : IApiEngine
{

View file

@ -5,8 +5,9 @@ using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
namespace GamecraftModdingAPI.Commands
namespace TechbloxModdingAPI.Commands
{
/// <summary>
/// A simple implementation of ICustomCommandEngine sufficient for most commands.
@ -29,18 +30,20 @@ namespace GamecraftModdingAPI.Commands
/// </summary>
private Action runCommand;
public IEntitiesDB entitiesDB { set; private get; }
public EntitiesDB entitiesDB { set; private get; }
public void Dispose()
public bool isRemovable => true;
public void Dispose()
{
GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"Unregistering SimpleCustomCommandEngine {this.Name}");
Logging.MetaDebugLog($"Unregistering SimpleCustomCommandEngine {this.Name}");
CommandRegistrationHelper.Unregister(this.Name);
}
public void Ready()
{
GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"Registering SimpleCustomCommandEngine {this.Name}");
CommandRegistrationHelper.Register(this.Name, this.runCommand, this.Description);
Logging.MetaDebugLog($"Registering SimpleCustomCommandEngine {this.Name}");
CommandRegistrationHelper.Register(this.Name, this.InvokeCatchError, this.Description);
}
/// <summary>
@ -55,5 +58,24 @@ namespace GamecraftModdingAPI.Commands
this.Name = name;
this.Description = description;
}
public void Invoke()
{
runCommand();
}
private void InvokeCatchError()
{
try
{
runCommand();
}
catch (Exception e)
{
CommandRuntimeException wrappedException = new CommandRuntimeException($"Command {Name} threw an exception when executed", e);
Logging.LogWarning(wrappedException.ToString());
Logging.CommandLogError(wrappedException.ToString());
}
}
}
}

View file

@ -5,8 +5,9 @@ using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
namespace GamecraftModdingAPI.Commands
namespace TechbloxModdingAPI.Commands
{
/// <summary>
/// A simple implementation of ICustomCommandEngine sufficient for most commands.
@ -20,18 +21,20 @@ namespace GamecraftModdingAPI.Commands
private Action<A> runCommand;
public IEntitiesDB entitiesDB { set; private get; }
public EntitiesDB entitiesDB { set; private get; }
public void Dispose()
public bool isRemovable => true;
public void Dispose()
{
GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"Unregistering SimpleCustomCommandEngine {this.Name}");
Logging.MetaDebugLog($"Unregistering SimpleCustomCommandEngine {this.Name}");
CommandRegistrationHelper.Unregister(this.Name);
}
public void Ready()
{
GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"Registering SimpleCustomCommandEngine {this.Name}");
CommandRegistrationHelper.Register<A>(this.Name, this.runCommand, this.Description);
Logging.MetaDebugLog($"Registering SimpleCustomCommandEngine {this.Name}");
CommandRegistrationHelper.Register<A>(this.Name, this.InvokeCatchError, this.Description);
}
/// <summary>
@ -45,6 +48,25 @@ namespace GamecraftModdingAPI.Commands
this.runCommand = command;
this.Name = name;
this.Description = description;
}
public void Invoke(A a)
{
runCommand(a);
}
private void InvokeCatchError(A a)
{
try
{
runCommand(a);
}
catch (Exception e)
{
CommandRuntimeException wrappedException = new CommandRuntimeException($"Command {Name} threw an exception when executed", e);
Logging.LogWarning(wrappedException.ToString());
Logging.CommandLogError(wrappedException.ToString());
}
}
}
}

View file

@ -5,8 +5,9 @@ using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
namespace GamecraftModdingAPI.Commands
namespace TechbloxModdingAPI.Commands
{
/// <summary>
/// A simple implementation of ICustomCommandEngine sufficient for most commands.
@ -20,18 +21,20 @@ namespace GamecraftModdingAPI.Commands
private Action<A,B> runCommand;
public IEntitiesDB entitiesDB { set; private get; }
public EntitiesDB entitiesDB { set; private get; }
public void Dispose()
public bool isRemovable => true;
public void Dispose()
{
GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"Unregistering SimpleCustomCommandEngine {this.Name}");
Logging.MetaDebugLog($"Unregistering SimpleCustomCommandEngine {this.Name}");
CommandRegistrationHelper.Unregister(this.Name);
}
public void Ready()
{
GamecraftModdingAPI.Utility.Logging.MetaDebugLog($"Registering SimpleCustomCommandEngine {this.Name}");
CommandRegistrationHelper.Register<A,B>(this.Name, this.runCommand, this.Description);
Logging.MetaDebugLog($"Registering SimpleCustomCommandEngine {this.Name}");
CommandRegistrationHelper.Register<A,B>(this.Name, this.InvokeCatchError, this.Description);
}
/// <summary>
@ -45,6 +48,25 @@ namespace GamecraftModdingAPI.Commands
this.runCommand = command;
this.Name = name;
this.Description = description;
}
public void Invoke(A a, B b)
{
runCommand(a, b);
}
private void InvokeCatchError(A a, B b)
{
try
{
runCommand(a, b);
}
catch (Exception e)
{
CommandRuntimeException wrappedException = new CommandRuntimeException($"Command {Name} threw an exception when executed", e);
Logging.LogWarning(wrappedException.ToString());
Logging.CommandLogError(wrappedException.ToString());
}
}
}
}

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