Compare commits

..

81 Commits

Author SHA1 Message Date
6d3f9f8afb packet parsing optimisations 2026-02-17 16:35:20 +02:00
1fa8516ebe byte encoder 2026-02-17 15:33:20 +02:00
3dbe62a64d more sync fixes 2026-02-17 15:31:23 +02:00
Aaro Varis
6d7f9aa8c0 Optimize ByteEncoder.cs 2026-02-17 14:37:29 +02:00
671d8e4ad6 Rename isBlacklistedPool to IsBlacklistedPool; simplify transform check
Renamed PoolBlacklist.isBlacklistedPool to IsBlacklistedPool for C# naming consistency and updated all references. Simplified InPoolManagerTransform to check root object's name instead of path for improved reliability.
2026-01-13 17:43:23 +02:00
335df69fcd Refactor event patching and transport systems
- Convert TransportBase from abstract class to interface; replace BORADCAST_ID constant with BROADCAST_ID property in transport implementations.
- Update SteamTransport to implement the new interface and provide its own BROADCAST_ID.
- Refactor event patching: introduce UnityEventPatching to map proxy events to originals, and update UnityEventPatch<T> to use proxy events for patching.
- Ensure patched UnityEvents replace originals in SyncablePhysics, so all invocations go through patch logic.
- Improve robustness and maintainability of both networking and event patching systems.
2026-01-13 17:34:02 +02:00
54ec8da1fe invalid optimization test 2025-10-19 21:38:22 +03:00
f6f484549a idk whar i did 2025-07-24 00:25:01 +03:00
5f148bc86f update sync coroutine, holsters broken 2025-03-30 23:39:16 +03:00
b72add4e81 adjust sync 2025-03-30 15:45:22 +03:00
b9390bbf03 patch changes 2025-03-28 22:30:09 +02:00
f5ad619ed5 adjust how syncable cache works 2025-03-24 17:34:48 +02:00
Aaro Varis
8edfcff56f updated variable name 2025-03-24 17:29:41 +02:00
Aaro Varis
e29f0ebb70 maybe fixed static buttons 2025-03-24 17:27:46 +02:00
Aaro Varis
77a6101e08 playerscript changes 2025-03-24 13:03:13 +02:00
Aaro Varis
9a468564e8 mogging 2025-03-23 23:54:44 +02:00
Aaro Varis
b004b725f0 changes 2025-03-23 20:46:20 +02:00
Aaro Varis
ff52da9542 fix the broken :D 2025-03-23 20:19:39 +02:00
Aaro Varis
3d879a605d !BROKEN!!! 2025-03-23 19:59:59 +02:00
Aaro Varis
143505f762 debug log statements 2025-03-23 00:22:16 +02:00
21e44cc4c7 Pool patch stuff 2025-03-18 20:18:34 +02:00
ba6d71a2ec testing despawn patch 2025-03-17 16:55:01 +02:00
f06af0647d fix fix 2025-03-17 16:37:26 +02:00
253f29f5be fix button method patch 2025-03-17 16:37:09 +02:00
Aaro Varis
64b2005beb mm 2025-03-16 14:31:11 +02:00
Aaro Varis
922c33d4a1 Simple event queue for better experience 2025-03-16 13:25:25 +02:00
Aaro Varis
2f27e39610 disable some zone patches 2025-03-14 23:36:19 +02:00
Aaro Varis
104c52c183 Aaa 2025-03-14 21:30:03 +02:00
95cd554eb0 add oneshot event patch 2025-03-14 14:21:12 +02:00
f1f8f1cbcf scenemanager sync 2025-03-13 20:46:57 +02:00
1843410a4d local only trigger 2025-03-13 20:15:58 +02:00
3f359ed4ec logging fixes 2025-03-13 20:07:19 +02:00
632230b8b2 Button event sync and custom logger stuff 2025-03-13 20:02:38 +02:00
756deee097 tried to create a universal unity event hook 2025-03-12 18:35:16 +02:00
Aaro Varis
a7769147e3 build script stuff 2025-03-10 18:14:29 +02:00
Aaro Varis
3238fb7acf fuck 2025-03-10 18:03:13 +02:00
Aaro Varis
92ab39ed69 probs broke everything 2025-03-10 18:01:18 +02:00
Aaro Varis
f27e325526 . 2025-03-10 03:58:08 +02:00
Aaro Varis
a7dc4441a7 a 2025-03-10 03:51:36 +02:00
Aaro Varis
68aa949591 changed some zone patch stuff 2025-03-10 03:44:33 +02:00
Aaro Varis
a479fa8dd6 adjust plugsync reliability 2025-03-10 01:55:20 +02:00
Aaro Varis
52f3d57721 enable periodic plug sync 2025-03-10 01:54:50 +02:00
Aaro Varis
00f0a33a47 Fix stuff? 2025-03-10 01:54:27 +02:00
Aaro Varis
f9db5be09b zone patches 2025-03-09 23:15:51 +02:00
Aaro Varis
3b0c26a687 Packet catchup and scene indexes in packets 2025-03-09 22:32:47 +02:00
Aaro Varis
20a624fb0f AI Sync initial changes 2025-03-09 21:42:39 +02:00
Aaro Varis
adbd7f4704 mogus 2025-03-09 01:16:27 +02:00
Aaro Varis
eb66d0b089 remove the most spammy debug logs 2025-03-09 01:15:18 +02:00
Aaro Varis
a892e80068 more player sync stuff 2025-03-09 01:09:04 +02:00
Aaro Varis
7bc1b21098 Player sync styff 2025-03-08 22:36:48 +02:00
Aaro Varis
252f6ba49a Player Sync stuff 2025-03-08 17:45:29 +02:00
Aaro Varis
c0e912be87 more gun sync stuff 2025-03-08 00:16:26 +02:00
Aaro Varis
0030f68f99 Mag and gun sync fixes 2025-03-07 23:07:39 +02:00
Aaro Varis
28715ef80d holster sync fixes and gun sync fixes 2025-03-07 18:58:39 +02:00
54301cbf37 aaaaaa 2025-03-07 16:42:45 +02:00
84e3efe3e9 aaaa 2025-03-06 21:05:42 +02:00
2c8f1af97d sync fixes 2025-03-06 21:02:00 +02:00
44776c3499 Mag sync epicly working 2025-03-06 20:33:22 +02:00
5f0b1d40fb aaaaaa 2025-03-06 18:39:55 +02:00
2004f9cd0d Work on mag sync, broken af atm 2025-03-05 21:08:10 +02:00
4bd96e59bd PlugSync stuff 2025-03-05 15:35:18 +02:00
8c887ae0e4 Stuff Patches 2025-03-04 21:01:46 +02:00
03127bd810 Zone patches 2025-03-04 18:50:41 +02:00
7ab5f9779b AAAAAA 2025-03-04 15:09:34 +02:00
7b5b4b2744 aaaa 2025-03-03 21:01:50 +02:00
bc10f6b1b7 aaaaaaaa 2025-03-03 20:57:12 +02:00
e2e91ce513 update build event 2025-03-03 20:11:33 +02:00
51d7d81605 aaaaa 2025-03-03 20:10:39 +02:00
6402afc72b pool changes 2025-03-03 16:23:40 +02:00
Aaro Varis
c11256f9a1 aaa 2025-03-02 18:15:49 +02:00
Aaro Varis
9295ba43e3 Mogus 2025-03-02 18:12:35 +02:00
Aaro Varis
b7bd646520 loottable patches 2025-03-02 17:55:18 +02:00
Aaro Varis
7f266321db more sync stuff 2025-03-02 17:35:20 +02:00
Aaro Varis
fc744fd1ab network stuff 2025-03-02 11:45:15 +02:00
Aaro Varis
055ee44da5 Untested discard message 2025-03-02 01:42:55 +02:00
Aaro Varis
b485ab3259 Client spawning fix 2025-03-02 01:30:39 +02:00
Aaro Varis
76062e026e Ownership transfer and shit, and some pool stuff 2025-03-01 23:05:46 +02:00
Aaro Varis
061e7e6d8d Bunch of changes 2025-03-01 00:15:23 +02:00
d0eb7f89be Bunch more networking stuff 2025-02-28 14:35:14 +02:00
1dce1960f6 More Networking stuff 2025-02-28 12:20:55 +02:00
ff70bad234 Mogus 2025-02-28 11:13:16 +02:00
64 changed files with 5119 additions and 857 deletions

13
.idea/.idea.BoneSync/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/.idea.BoneSync.iml
/projectSettingsUpdater.xml
/modules.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

6
.idea/.idea.BoneSync/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\xunit.runner.visualstudio.3.0.2\build\net472\xunit.runner.visualstudio.props" Condition="Exists('..\packages\xunit.runner.visualstudio.3.0.2\build\net472\xunit.runner.visualstudio.props')" />
<Import Project="..\packages\xunit.core.2.9.3\build\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.9.3\build\xunit.core.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -45,7 +43,7 @@
<ItemGroup> <ItemGroup>
<Reference Include="0Harmony, Version=2.9.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="0Harmony, Version=2.9.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\0Harmony.dll</HintPath> <HintPath>A:\SteamLibrary\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\0Harmony.dll</HintPath>
</Reference> </Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\Assembly-CSharp.dll</HintPath> <HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\Assembly-CSharp.dll</HintPath>
@@ -57,24 +55,9 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\MelonLoader.dll</HintPath> <HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\MelonLoader.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.12.0\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.PlatformAbstractions, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.12.0\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.ObjectModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.12.0\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Reflection.Metadata, Version=1.4.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.1.6.0\lib\netstandard2.0\System.Reflection.Metadata.dll</HintPath>
</Reference>
<Reference Include="System.Runtime" /> <Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Serialization" /> <Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@@ -92,6 +75,10 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.AnimationModule.dll</HintPath> <HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.AnimationModule.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.AssetBundleModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>A:\SteamLibrary\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.AssetBundleModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule"> <Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath> <HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference> </Reference>
@@ -99,26 +86,66 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath> <HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
</Reference> </Reference>
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL"> <Reference Include="UnityEngine.TextRenderingModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll</HintPath> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
</Reference> </Reference>
<Reference Include="xunit.assert, Version=2.9.3.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL"> <Reference Include="UnityEngine.UI, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.9.3\lib\netstandard1.1\xunit.assert.dll</HintPath> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
</Reference> </Reference>
<Reference Include="xunit.core, Version=2.9.3.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL"> <Reference Include="UnityEngine.UIElementsModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.9.3\lib\net452\xunit.core.dll</HintPath> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
</Reference> </Reference>
<Reference Include="xunit.execution.desktop, Version=2.9.3.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL"> <Reference Include="UnityEngine.UIModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.9.3\lib\net452\xunit.execution.desktop.dll</HintPath> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\MelonLoader\Managed\UnityEngine.UIModule.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Data\ByteEncoder.cs" />
<Compile Include="Data\Debugger.cs" />
<Compile Include="Data\EmebeddedAssetBundle.cs" />
<Compile Include="Data\PlayerScripts.cs" />
<Compile Include="Data\SpawnableManager.cs" />
<Compile Include="Data\Structs.cs" />
<Compile Include="Networking\Messages\AISyncMessage.cs" />
<Compile Include="Networking\Messages\DiscardSyncableMessage.cs" />
<Compile Include="Networking\Messages\GunSyncMessage.cs" />
<Compile Include="Networking\Messages\SceneChangeMessage.cs" />
<Compile Include="Patching\AIHealthPatches.cs" />
<Compile Include="Patching\ButtonTogglePatches.cs" />
<Compile Include="Patching\CartPatches.cs" />
<Compile Include="Patching\HolsterSlotPatches.cs" />
<Compile Include="Networking\Messages\MagazineSyncMessage.cs" />
<Compile Include="Networking\Messages\ObjectDamageMessage.cs" />
<Compile Include="Networking\Messages\ObjectSyncMessage.cs" /> <Compile Include="Networking\Messages\ObjectSyncMessage.cs" />
<Compile Include="Networking\Messages\OwnershipTransferMessage.cs" />
<Compile Include="Networking\Messages\PlugSyncMessage.cs" />
<Compile Include="Networking\Messages\RegisterSyncableMessage.cs" /> <Compile Include="Networking\Messages\RegisterSyncableMessage.cs" />
<Compile Include="Networking\Messages\SimpleSyncableEventMessage.cs" />
<Compile Include="Patching\CallPatchedMethod.cs" />
<Compile Include="Patching\DebugPatches.cs" />
<Compile Include="Patching\GripPatches.cs" />
<Compile Include="Patching\GunPatches.cs" />
<Compile Include="Patching\InteractableHostPatches.cs" /> <Compile Include="Patching\InteractableHostPatches.cs" />
<Compile Include="Sync\Components\Syncable.cs" /> <Compile Include="Patching\ObjectHealthPatches.cs" />
<Compile Include="Patching\PlugPatches.cs" />
<Compile Include="Patching\PrefabSpawnerPatches.cs" />
<Compile Include="Patching\SceneManagerPatches.cs" />
<Compile Include="Patching\SkeletonHandPatches.cs" />
<Compile Include="Patching\UnityEventPatching.cs" />
<Compile Include="Patching\ZonePatches.cs" />
<Compile Include="Sync\Components\SyncableAI.cs" />
<Compile Include="Sync\Components\SyncableBase.cs" />
<Compile Include="Sync\Components\SyncableDamage.cs" />
<Compile Include="Sync\Components\SyncableNetworking.cs" />
<Compile Include="Sync\Components\SyncablePhysics.cs" />
<Compile Include="Sync\Components\SyncablePlugs.cs" />
<Compile Include="Sync\Components\SyncableProperties.cs" />
<Compile Include="Sync\ObjectSync.cs" /> <Compile Include="Sync\ObjectSync.cs" />
<Compile Include="Sync\PlayerSync.cs" /> <Compile Include="Sync\ObjectSyncCache.cs" />
<Compile Include="Facepunch.Steamworks\Callbacks\CallResult.cs" /> <Compile Include="Facepunch.Steamworks\Callbacks\CallResult.cs" />
<Compile Include="Facepunch.Steamworks\Callbacks\ICallbackData.cs" /> <Compile Include="Facepunch.Steamworks\Callbacks\ICallbackData.cs" />
<Compile Include="Facepunch.Steamworks\Classes\AuthTicket.cs" /> <Compile Include="Facepunch.Steamworks\Classes\AuthTicket.cs" />
@@ -272,39 +299,24 @@
<Compile Include="Networking\Messages\PlayerSyncMessage.cs" /> <Compile Include="Networking\Messages\PlayerSyncMessage.cs" />
<Compile Include="Networking\Packet.cs" /> <Compile Include="Networking\Packet.cs" />
<Compile Include="Networking\PacketTypes.cs" /> <Compile Include="Networking\PacketTypes.cs" />
<Compile Include="Networking\Structs.cs" />
<Compile Include="Networking\Transport\SteamTransport.cs" /> <Compile Include="Networking\Transport\SteamTransport.cs" />
<Compile Include="Networking\Transport\TransportBase.cs" /> <Compile Include="Networking\Transport\TransportBase.cs" />
<Compile Include="Patching\PoolPatches.cs" /> <Compile Include="Patching\PoolPatches.cs" />
<Compile Include="Player\PlayerRig.cs" /> <Compile Include="Player\PlayerRig.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Networking\ByteEncoder.cs" />
<Compile Include="Sync\SceneSync.cs" /> <Compile Include="Sync\SceneSync.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\xunit.analyzers.1.18.0\analyzers\dotnet\cs\xunit.analyzers.dll" />
<Analyzer Include="..\packages\xunit.analyzers.1.18.0\analyzers\dotnet\cs\xunit.analyzers.fixes.dll" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Facepunch.Steamworks\LICENSE.txt" /> <Content Include="Facepunch.Steamworks\LICENSE.txt" />
<EmbeddedResource Include="steam_api.dll" /> <EmbeddedResource Include="steam_api.dll" />
<EmbeddedResource Include="steam_api64.dll" /> <EmbeddedResource Include="steam_api64.dll" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup>
<EmbeddedResource Include="playerrep.eres" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>xcopy /Y "$(TargetPath)" "C:\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\Mods\"</PostBuildEvent> <PostBuildEvent>xcopy /Y "$(TargetPath)" "A:\SteamLibrary\steamapps\common\BONEWORKS\BONEWORKS\Mods\"
xcopy /Y "$(TargetPath)" "C:\Program Files (x86)\Steam\steamapps\common\BONEWORKS\BONEWORKS\Mods\"</PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\xunit.core.2.9.3\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.9.3\build\xunit.core.props'))" />
<Error Condition="!Exists('..\packages\xunit.core.2.9.3\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.9.3\build\xunit.core.targets'))" />
<Error Condition="!Exists('..\packages\xunit.runner.visualstudio.3.0.2\build\net472\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.runner.visualstudio.3.0.2\build\net472\xunit.runner.visualstudio.props'))" />
</Target>
<Import Project="..\packages\xunit.core.2.9.3\build\xunit.core.targets" Condition="Exists('..\packages\xunit.core.2.9.3\build\xunit.core.targets')" />
</Project> </Project>

View File

@@ -0,0 +1,384 @@
using BoneSync.Networking.Messages;
using StressLevelZero;
using StressLevelZero.Combat;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace BoneSync.Data
{
public static class BitPacking
{
public static byte[] PackBits(bool[] bits)
{
int byteCount = bits.Length / 8;
if (bits.Length % 8 != 0)
{
byteCount++;
}
byte[] bytes = new byte[byteCount];
for (int i = 0; i < bits.Length; i++)
{
int byteIndex = i / 8;
int bitIndex = i % 8;
if (bits[i])
{
bytes[byteIndex] |= (byte)(1 << bitIndex);
}
}
return bytes;
}
public static bool[] UnpackBits(byte[] bytes, int bitCount)
{
bool[] bits = new bool[bitCount];
for (int i = 0; i < bitCount; i++)
{
int byteIndex = i / 8;
int bitIndex = i % 8;
bits[i] = (bytes[byteIndex] & (1 << bitIndex)) != 0;
}
return bits;
}
}
internal class ByteEncoder
{
private MemoryStream stream;
private BinaryWriter writer;
private BinaryReader reader;
private bool isWriting;
public ByteEncoder()
{
stream = new MemoryStream();
writer = new BinaryWriter(stream);
isWriting = true;
}
public ByteEncoder(byte[] data)
{
stream = new MemoryStream(data);
reader = new BinaryReader(stream);
isWriting = false;
}
public byte[] ToArray()
{
byte[] buf = stream.GetBuffer();
int start, len;
if (isWriting)
{
start = 0;
len = (int)stream.Length;
}
else
{
start = (int)stream.Position;
len = (int)stream.Length - start;
}
byte[] res = new byte[len];
Array.Copy(buf, start, res, 0, len);
return res;
}
public void SetBytes(byte[] data)
{
if (stream != null)
{
stream.Dispose();
}
stream = new MemoryStream(data);
reader = new BinaryReader(stream);
writer = null;
isWriting = false;
}
public void WriteByte(byte value)
{
writer.Write(value);
}
public void WriteBytes(byte[] value)
{
writer.Write(value);
}
public byte[] ReadBytes(int count)
{
byte[] value = reader.ReadBytes(count);
if (value.Length != count)
{
throw new Exception("Not enough data to read, expected " + count + " but only have " + value.Length);
}
return value;
}
public byte ReadByte()
{
return reader.ReadByte();
}
public void WriteBool(bool value)
{
WriteByte((byte)(value ? 1 : 0));
}
public bool ReadBool()
{
return ReadByte() == 1;
}
public void WriteInt(int value)
{
writer.Write(value);
}
public int ReadInt()
{
return reader.ReadInt32();
}
public void WriteLong(long value)
{
writer.Write(value);
}
public long ReadLong()
{
return reader.ReadInt64();
}
public void WriteFloat(float value)
{
writer.Write(value);
}
public float ReadFloat()
{
return reader.ReadSingle();
}
public void WriteUShort(ushort value)
{
writer.Write(value);
}
public ushort ReadUShort()
{
return reader.ReadUInt16();
}
public void WriteDouble(double value)
{
writer.Write(value);
}
public double ReadDouble()
{
return reader.ReadDouble();
}
public void WriteString(string value)
{
byte[] bytes = Encoding.UTF8.GetBytes(value);
WriteInt(bytes.Length);
WriteBytes(bytes);
}
public string ReadString()
{
int length = ReadInt();
return Encoding.UTF8.GetString(ReadBytes(length));
}
public void WriteULong(ulong value)
{
writer.Write(value);
}
public ulong ReadULong()
{
return reader.ReadUInt64();
}
public void WriteVector3(Vector3 value)
{
WriteFloat(value.x);
WriteFloat(value.y);
WriteFloat(value.z);
}
public Vector3 ReadVector3()
{
float x = ReadFloat();
float y = ReadFloat();
float z = ReadFloat();
return new Vector3(x, y, z);
}
public void WriteQuaternion(Quaternion value)
{
WriteVector3(value.eulerAngles);
}
public Quaternion ReadQuaternion()
{
Vector3 eulerAngles = ReadVector3();
return Quaternion.Euler(eulerAngles);
}
public void WriteSimpleTransform(SimpleSyncTransform value)
{
WriteVector3(value.position);
WriteQuaternion(value.rotation);
WriteBool(value.scale.HasValue);
if (value.scale.HasValue)
WriteVector3(value.scale.Value);
}
public SimpleSyncTransform ReadSimpleTransform()
{
Vector3 position = ReadVector3();
Quaternion rotation = ReadQuaternion();
bool hasScale = ReadBool();
Vector3? scale = null;
if (hasScale)
{
scale = ReadVector3();
}
return new SimpleSyncTransform()
{
position = position,
rotation = rotation,
scale = scale
};
}
public void WriteMatrix4x4(Matrix4x4 matrix)
{
for (int i = 0; i < 16; i++)
{
WriteFloat(matrix[i]);
}
}
public Matrix4x4 ReadMatrix4x4()
{
Matrix4x4 matrix = new Matrix4x4();
for (int i = 0; i < 16; i++)
{
matrix[i] = ReadFloat();
}
return matrix;
}
public void WriteBoolArray(bool[] array)
{
byte[] bytes = BitPacking.PackBits(array);
WriteByte((byte)bytes.Length);
WriteBytes(bytes);
}
public bool[] ReadBoolArray()
{
byte length = ReadByte();
byte[] bytes = ReadBytes(length);
return BitPacking.UnpackBits(bytes, length * 8);
}
public void WriteAmmoVariables(AmmoVariables ammoVariables)
{
WriteBool(ammoVariables.Tracer);
WriteFloat(ammoVariables.ProjectileMass);
WriteFloat(ammoVariables.ExitVelocity);
WriteFloat(ammoVariables.AttackDamage);
WriteByte((byte)ammoVariables.AttackType);
WriteByte((byte)ammoVariables.cartridgeType);
}
public AmmoVariables ReadAmmoVariables()
{
AmmoVariables ammoVariables = new AmmoVariables()
{
Tracer = ReadBool(),
ProjectileMass = ReadFloat(),
ExitVelocity = ReadFloat(),
AttackDamage = ReadFloat(),
AttackType = (AttackType)ReadByte(),
cartridgeType = (Cart)ReadByte()
};
return ammoVariables;
}
public void WriteMagazineData(MagazineData magazineData)
{
WriteByte((byte)magazineData.weight);
WriteByte((byte)magazineData.platform);
WriteUShort((byte)magazineData.cartridgeType);
BulletObject[] bulletObjects = magazineData.AmmoSlots;
WriteByte((byte)bulletObjects.Length);
for (int i = 0; i < bulletObjects.Length; i++)
{
WriteAmmoVariables(bulletObjects[i].ammoVariables);
}
}
public MagazineData ReadMagazineData()
{
MagazineData mag = new MagazineData();
mag.weight = (Weight)ReadByte();
mag.platform = (Platform)ReadByte();
mag.cartridgeType = (Cart)ReadUShort();
byte bulletCount = ReadByte();
mag.AmmoSlots = new BulletObject[bulletCount];
for (int i = 0; i < bulletCount; i++)
{
mag.AmmoSlots[i] = new BulletObject()
{
ammoVariables = ReadAmmoVariables()
};
}
return mag;
}
public void WriteCompressedFloat(float value)
{
value = Mathf.Clamp01(value);
int rounded = Mathf.FloorToInt(value * 255f);
WriteByte((byte)rounded);
}
public float ReadCompressedFloat()
{
return ReadByte() / 255f;
}
public void WriteFingerCurl(SimpleFingerCurl fingerCurl)
{
WriteCompressedFloat(fingerCurl.thumb);
WriteCompressedFloat(fingerCurl.index);
WriteCompressedFloat(fingerCurl.middle);
WriteCompressedFloat(fingerCurl.ring);
WriteCompressedFloat(fingerCurl.pinky);
}
public SimpleFingerCurl ReadFingerCurl()
{
SimpleFingerCurl fingerCurl = new SimpleFingerCurl()
{
thumb = ReadCompressedFloat(),
index = ReadCompressedFloat(),
middle = ReadCompressedFloat(),
ring = ReadCompressedFloat(),
pinky = ReadCompressedFloat()
};
return fingerCurl;
}
}
}

110
BoneSync/Data/Debugger.cs Normal file
View File

@@ -0,0 +1,110 @@
using MelonLoader;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
namespace BoneSync.Data
{
internal static class SyncLogger
{
private static string GetCleanCaller(string className, string methodName)
{
if (className.Contains("BoneSync"))
className = className.Replace("BoneSync.", "");
if (methodName.Contains("BoneSync"))
methodName = methodName.Replace("BoneSync.", "");
return $"{className}.{methodName}";
}
public static void Msg(string message, ConsoleColor color = ConsoleColor.White, int frame = 1)
{
StackTrace stackTrace = new StackTrace();
StackFrame stackFrame = stackTrace.GetFrame(frame);
MethodBase methodBase = stackFrame.GetMethod();
string methodName = methodBase.Name;
string className = methodBase.DeclaringType.Name;
string cleanCaller = GetCleanCaller(className, methodName);
MelonLogger.Msg(color, $"[{cleanCaller}] {message}");
}
public static void Warning(string message)
{
Msg(message, ConsoleColor.Yellow, 2);
}
public static void Error(string message)
{
Msg(message, ConsoleColor.Red, 2);
}
internal static void Debug(string v)
{
#if DEBUG
Msg($"[DEBUG] {v}", ConsoleColor.Gray, 2);
#endif
}
}
/*internal static class SyncDebugUI
{
public static Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
public static Text debugText;
public static void CreateUI()
{
if (debugText != null)
return;
GameObject canvas = new GameObject("BoneSyncDebugCanvas");
canvas.AddComponent<Canvas>();
canvas.AddComponent<CanvasScaler>();
canvas.AddComponent<GraphicRaycaster>();
canvas.GetComponent<Canvas>().renderMode = RenderMode.ScreenSpaceOverlay;
GameObject panel = new GameObject("BoneSyncDebugPanel");
panel.transform.SetParent(canvas.transform);
panel.AddComponent<CanvasRenderer>();
panel.AddComponent<Image>();
panel.GetComponent<Image>().color = new Color(0, 0, 0, 0.5f);
panel.GetComponent<RectTransform>().anchorMin = new Vector2(0, 0);
panel.GetComponent<RectTransform>().anchorMax = new Vector2(1, 1);
panel.GetComponent<RectTransform>().offsetMin = new Vector2(0, 0);
panel.GetComponent<RectTransform>().offsetMax = new Vector2(0, 0);
GameObject text = new GameObject("BoneSyncDebugText");
text.transform.SetParent(panel.transform);
text.AddComponent<RectTransform>();
text.AddComponent<Text>();
text.GetComponent<Text>().font = Font.CreateDynamicFontFromOSFont("Arial", 12);
text.GetComponent<Text>().color = Color.white;
text.GetComponent<Text>().alignment = TextAnchor.UpperLeft;
text.GetComponent<Text>().resizeTextForBestFit = true;
text.GetComponent<Text>().resizeTextMaxSize = 12;
text.GetComponent<Text>().resizeTextMinSize = 8;
text.GetComponent<Text>().text = "BoneSync Debug";
text.GetComponent<RectTransform>().anchorMin = new Vector2(0, 0);
text.GetComponent<RectTransform>().anchorMax = new Vector2(1, 1);
text.GetComponent<RectTransform>().offsetMin = new Vector2(0, 0);
text.GetComponent<RectTransform>().offsetMax = new Vector2(0, 0);
debugText = text.GetComponent<Text>();
}
public static void UpdateUI()
{
CreateUI();
StringBuilder sb = new StringBuilder();
foreach (var kvp in keyValuePairs)
{
sb.AppendLine($"{kvp.Key}: {kvp.Value}");
}
debugText.text = sb.ToString();
}
}*/
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
namespace BoneSync.Data
{
public static class EmebeddedAssetBundle
{
// Credit to the "Entanglement" mod. The playerrep asset bundle is also by them.
public static AssetBundle LoadFromAssembly(string name)
{
Assembly assembly = Assembly.GetExecutingAssembly();
string[] manifestResources = assembly.GetManifestResourceNames();
if (manifestResources.Contains(name))
{
SyncLogger.Msg($"Loading embedded bundle data {name}...");
byte[] bytes;
using (Stream str = assembly.GetManifestResourceStream(name))
using (MemoryStream memoryStream = new MemoryStream())
{
str.CopyTo(memoryStream);
bytes = memoryStream.ToArray();
}
SyncLogger.Msg($"Loading bundle from data {name}, please be patient...");
AssetBundle temp = AssetBundle.LoadFromMemory(bytes);
SyncLogger.Msg($"Done!");
return temp;
}
SyncLogger.Warning($"Failed to load embedded bundle data {name}! Bundle not found.");
return null;
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using StressLevelZero.Interaction;
using StressLevelZero.Player;
using StressLevelZero.Rig;
using StressLevelZero.VRMK;
using UnhollowerBaseLib;
using UnityEngine;
namespace BoneSync.Data
{
// copied from Entanglement mod
public static class PlayerScripts
{
public static GameObject localPlayerGameObject;
public static RigManager playerRig;
public static PhysBody playerPhysBody;
public static Player_Health playerHealth;
public static PhysGrounder playerGrounder;
public static Hand playerLeftHand;
public static Hand playerRightHand;
public static bool reloadLevelOnDeath;
public static RuntimeAnimatorController playerAnimatorController;
public static Il2CppStringArray playerHandPoses = null;
public static void GetPlayerScripts()
{
if (playerRig != null)
return;
localPlayerGameObject = GameObject.Find("[RigManager (Default Brett)]");
playerRig = localPlayerGameObject.GetComponentInChildren<RigManager>();
playerHealth = playerRig.playerHealth;
reloadLevelOnDeath = playerHealth.reloadLevelOnDeath;
playerHealth.reloadLevelOnDeath = !BoneSync.IsConnected;
try
{
PhysicsRig physicsRig = playerRig.physicsRig;
playerPhysBody = physicsRig.physBody;
playerGrounder = playerPhysBody.physG;
playerLeftHand = physicsRig.leftHand;
playerRightHand = physicsRig.rightHand;
playerAnimatorController = playerRig.gameWorldSkeletonRig.characterAnimationManager.animator.runtimeAnimatorController;
} catch
{
SyncLogger.Warning("Failed to get physicsRig player scripts!");
}
}
public static void GetHandPoses()
{
// Checks if we already got the hand poses to prevent crashes
if (playerHandPoses == null)
CharacterAnimationManager.FetchHandPoseList(out playerHandPoses); // Lets hope this is constant!
}
}
}

View File

@@ -0,0 +1,84 @@
using BoneSync.Patching;
using MelonLoader;
using StressLevelZero.Data;
using StressLevelZero.Pool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace BoneSync.Data
{
public static class SpawnableManager
{
private static Dictionary<string, SpawnableObject> _spawnableCache = new Dictionary<string, SpawnableObject>();
private static Dictionary<string, SpawnableObject> _spawnablePrefabNameCache = new Dictionary<string, SpawnableObject>();
public static void AddUnregisteredSpawnables()
{
Il2CppReferenceArray<UnityObject> foundSpawnables = UnityObject.FindObjectsOfTypeIncludingAssets(Il2CppType.Of<SpawnableObject>());
foreach (UnityObject obj in foundSpawnables)
{
SpawnableObject spawnable = obj.Cast<SpawnableObject>();
RegisterSpawnable(spawnable);
}
}
private static void _ClearCache()
{
_spawnableCache.Clear();
_spawnablePrefabNameCache.Clear();
}
public static void RegisterSpawnable(SpawnableObject spawnable)
{
if (!_spawnableCache.ContainsKey(spawnable?.title))
_spawnableCache.Add(spawnable?.title, spawnable);
if (spawnable?.prefab?.name != null && !_spawnablePrefabNameCache.ContainsKey(spawnable.prefab.name))
_spawnablePrefabNameCache.Add(spawnable.prefab.name, spawnable);
}
public static SpawnableObject GetSpawnable(string title)
{
if (_spawnableCache.ContainsKey(title))
return _spawnableCache[title];
return null;
}
public static SpawnableObject GetSpawnableByPrefabName(string prefabName)
{
if (_spawnablePrefabNameCache.ContainsKey(prefabName))
return _spawnablePrefabNameCache[prefabName];
return null;
}
public static Pool GetPool(SpawnableObject spawnable) {
PoolManager.RegisterPool(spawnable);
return PoolManager.GetPool(spawnable.title);
}
public static Poolee SpawnPoolee(SpawnableObject spawnableObject, Vector3 position, Quaternion rotation)
{
if (spawnableObject == null) return null;
Pool pool = GetPool(spawnableObject);
if (pool == null)
{
SyncLogger.Warning("Failed to find pool: " + spawnableObject.title);
return null;
}
Poolee poolee = CallPatchedMethods.InstantiatePoolee(pool, position, rotation, pool.Prefab.transform.localScale);
return poolee;
}
public static void Initialize()
{
_ClearCache();
AddUnregisteredSpawnables();
}
}
}

View File

@@ -5,28 +5,28 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
namespace BoneSync.Networking namespace BoneSync.Data
{ {
public static class SimpleTransformExtensions public static class SimpleTransformExtensions
{ {
public static void ApplySimpleTransform(this UnityEngine.Transform transform, SimpleTransform simpleTransform) public static void ApplySimpleTransform(this Transform transform, SimpleSyncTransform simpleTransform)
{ {
transform.position = simpleTransform.position; transform.position = simpleTransform.position;
transform.rotation = simpleTransform.rotation; transform.rotation = simpleTransform.rotation;
transform.localScale = simpleTransform.scale; if (simpleTransform.scale.HasValue) transform.localScale = simpleTransform.scale.Value;
} }
} }
public struct SimpleTransform public struct SimpleSyncTransform
{ {
public Vector3 position; public Vector3 position;
public Quaternion rotation; public Quaternion rotation;
public Vector3 scale; public Vector3? scale;
public SimpleTransform(UnityEngine.Transform transform) public SimpleSyncTransform(Transform transform, bool withScale = true)
{ {
position = transform.position; position = transform.position;
rotation = transform.rotation; rotation = transform.rotation;
scale = transform.localScale; scale = withScale ? transform.localScale : (Vector3?)null;
} }
} }
} }

View File

@@ -7,6 +7,9 @@ using UnityEngine;
using BoneSync.Player; using BoneSync.Player;
using BoneSync.Sync; using BoneSync.Sync;
using Facepunch.Steamworks; using Facepunch.Steamworks;
using System.Reflection.Emit;
using BoneSync.Data;
using BoneSync.Networking.Messages;
namespace BoneSync namespace BoneSync
{ {
@@ -21,14 +24,18 @@ namespace BoneSync
public class BoneSync : MelonMod public class BoneSync : MelonMod
{ {
public static bool IsConnected
{
get; private set;
}
public static LobbyManager lobby; public static LobbyManager lobby;
public static TransportBase transport; public static ITransportBase transport;
public override void OnApplicationStart() public override void OnApplicationStart()
{ {
SteamClient.Init(823500, true); SteamClient.Init(823500, true);
PatchAll();
SteamLobbyManager steamLobbyManager = new SteamLobbyManager(); SteamLobbyManager steamLobbyManager = new SteamLobbyManager();
lobby = steamLobbyManager; lobby = steamLobbyManager;
@@ -36,49 +43,67 @@ namespace BoneSync
SceneSync.Initialize(); SceneSync.Initialize();
NetworkMessage.RegisterPacketTypes(); NetworkMessage.RegisterPacketTypes();
SpawnableManager.Initialize();
PlayerRig.LoadBundle();
PatchAll();
} }
public static void PatchAll() public static void PatchAll()
{ {
HarmonyLib.Harmony harmony = new HarmonyLib.Harmony("com.aarov.bonesync"); //HarmonyLib.Harmony harmony = new HarmonyLib.Harmony("com.aarov.bonesync");
harmony.PatchAll(); //harmony.i
} }
public override void OnSceneWasLoaded(int buildIndex, string sceneName) public override void OnSceneWasLoaded(int buildIndex, string sceneName)
{ {
//MelonLogger.Msg("OnLevelWasLoaded: " + sceneName); //SyncLogger.Msg("OnLevelWasLoaded: " + sceneName);
} }
public override void OnSceneWasInitialized(int buildIndex, string sceneName) public override void OnSceneWasInitialized(int buildIndex, string sceneName)
{ {
//MelonLogger.Msg("OnLevelWasInitialized: " + sceneName); //SyncLogger.Msg("OnLevelWasInitialized: " + sceneName);
SceneSync.OnSceneInit(buildIndex);
PlayerScripts.GetPlayerScripts();
} }
public override void OnSceneWasUnloaded(int buildIndex, string sceneName) public override void OnSceneWasUnloaded(int buildIndex, string sceneName)
{ {
//MelonLogger.Msg("OnLevelWasLoaded: " + sceneName); //SyncLogger.Msg("OnLevelWasLoaded: " + sceneName);
} }
public override void OnUpdate() public override void OnUpdate()
{ {
PlayerRig.LocalSyncTick();
transport.Tick(); transport.Tick();
//SyncDebugUI.UpdateUI();
//PlayerRig.Tick(); //PlayerRig.Tick();
if (Input.GetKeyDown(KeyCode.P)) if (Input.GetKeyDown(KeyCode.P))
{ {
MelonLogger.Msg("P key pressed"); SyncLogger.Msg("P key pressed");
//PlayerRig playerRig = PlayerRig.InstantiatePlayerRigPrefab(); //PlayerRig playerRig = PlayerRig.InstantiatePlayerRigPrefab();
} }
if (Input.GetKeyDown(KeyCode.I)) if (Input.GetKeyDown(KeyCode.I))
{ {
lobby.CreateLobby(); lobby.CreateLobby();
} }
}
public override void OnLevelWasInitialized(int level) if (Input.GetKeyDown(KeyCode.N))
{ {
SceneSync.OnSceneInit(level); SyncLogger.Msg("Creating debug player rig");
PlayerRig debugRig = PlayerRig.GetPlayerRig(0);
PlayerSyncInfo? playerSyncInfo = PlayerRig.GetLocalSyncInfo();
if (!playerSyncInfo.HasValue)
{
SyncLogger.Msg("PlayerSyncInfo is null");
}
debugRig.UpdatePlayerSync(playerSyncInfo.Value);
}
} }
public override void BONEWORKS_OnLoadingScreen() public override void BONEWORKS_OnLoadingScreen()
@@ -88,27 +113,30 @@ namespace BoneSync
public override void OnFixedUpdate() public override void OnFixedUpdate()
{ {
//MelonLogger.Msg("OnFixedUpdate"); IsConnected = lobby.IsConnected();
transport.Tick();
Packet.TryCatchup();
PlayerRig.Tick(Time.fixedDeltaTime);
} }
public override void OnLateUpdate() public override void OnLateUpdate()
{ {
//MelonLogger.Msg("OnLateUpdate"); //SyncLogger.Msg("OnLateUpdate");
} }
public override void OnGUI() public override void OnGUI()
{ {
//MelonLogger.Msg("OnGUI"); //SyncLogger.Msg("OnGUI");
} }
public override void OnApplicationQuit() public override void OnApplicationQuit()
{ {
//MelonLogger.Msg("OnApplicationQuit"); //SyncLogger.Msg("OnApplicationQuit");
} }
public override void OnPreferencesLoaded() public override void OnPreferencesLoaded()
{ {
//MelonLogger.Msg("OnPreferencesLoaded"); //SyncLogger.Msg("OnPreferencesLoaded");
} }
} }
} }

View File

@@ -1,172 +0,0 @@
using StressLevelZero;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BoneSync.Networking
{
internal class ByteEncoder
{
public List<byte> Data;
public ByteEncoder()
{
Data = new List<byte>();
}
public ByteEncoder(byte[] data)
{
Data = data.ToList();
}
public byte[] ToArray()
{
return Data.ToArray();
}
public void WriteByte(byte value)
{
Data.Add(value);
}
public void WriteBytes(byte[] value)
{
Data.AddRange(value);
}
public byte[] ReadBytes(int count)
{
if (Data.Count < count)
{
throw new Exception("Not enough data to read, expected " + count + " but only have " + Data.Count);
}
byte[] value = Data.GetRange(0, count).ToArray();
Data.RemoveRange(0, count);
return value;
}
public byte ReadByte()
{
byte value = Data[0];
Data.RemoveAt(0);
return value;
}
public void WriteInt(int value)
{
WriteBytes(BitConverter.GetBytes(value));
}
public int ReadInt()
{
return BitConverter.ToInt32(ReadBytes(sizeof(int)), 0);
}
public void WriteLong(long value)
{
WriteBytes(BitConverter.GetBytes(value));
}
public long ReadLong()
{
return BitConverter.ToInt64(ReadBytes(sizeof(long)), 0);
}
public void WriteFloat(float value)
{
WriteBytes(BitConverter.GetBytes(value));
}
public float ReadFloat()
{
return BitConverter.ToSingle(ReadBytes(sizeof(float)), 0);
}
public void WriteUShort(ushort value)
{
WriteBytes(BitConverter.GetBytes(value));
}
public ushort ReadUShort() {
return BitConverter.ToUInt16(ReadBytes(sizeof(ushort)), 0);
}
public void WriteDouble(double value)
{
WriteBytes(BitConverter.GetBytes(value));
}
public double ReadDouble()
{
return BitConverter.ToDouble(ReadBytes(sizeof(double)), 0);
}
public void WriteString(string value)
{
byte[] bytes = Encoding.UTF8.GetBytes(value);
WriteInt(bytes.Length);
WriteBytes(bytes);
}
public string ReadString()
{
int length = ReadInt();
return Encoding.UTF8.GetString(ReadBytes(length));
}
public void WriteUlong(ulong value)
{
WriteBytes(BitConverter.GetBytes(value));
}
public ulong ReadUlong()
{
return BitConverter.ToUInt64(ReadBytes(sizeof(ulong)), 0);
}
public void WriteVector3(UnityEngine.Vector3 value)
{
WriteFloat(value.x);
WriteFloat(value.y);
WriteFloat(value.z);
}
public UnityEngine.Vector3 ReadVector3()
{
float x = ReadFloat();
float y = ReadFloat();
float z = ReadFloat();
return new UnityEngine.Vector3(x, y, z);
}
public void WriteQuaternion(UnityEngine.Quaternion value)
{
WriteVector3(value.eulerAngles);
}
public UnityEngine.Quaternion ReadQuaternion()
{
UnityEngine.Vector3 eulerAngles = ReadVector3();
return UnityEngine.Quaternion.Euler(eulerAngles);
}
public void WriteSimpleTransform(SimpleTransform value)
{
WriteVector3(value.position);
WriteQuaternion(value.rotation);
WriteVector3(value.scale);
}
public SimpleTransform ReadSimpleTransform()
{
UnityEngine.Vector3 position = ReadVector3();
UnityEngine.Quaternion rotation = ReadQuaternion();
UnityEngine.Vector3 scale = ReadVector3();
return new SimpleTransform()
{
position = position,
rotation = rotation,
scale = scale
};
}
}
}

View File

@@ -1,4 +1,6 @@
using BoneSync.Sync; using BoneSync.Data;
using BoneSync.Player;
using BoneSync.Sync;
using Facepunch.Steamworks; using Facepunch.Steamworks;
using Facepunch.Steamworks.Data; using Facepunch.Steamworks.Data;
using MelonLoader; using MelonLoader;
@@ -17,46 +19,49 @@ namespace BoneSync.Networking.LobbyManager
SteamMatchmaking.OnLobbyCreated += (Result result, Lobby lobby) => SteamMatchmaking.OnLobbyCreated += (Result result, Lobby lobby) =>
{ {
_lobbyInstance = lobby; _lobbyInstance = lobby;
MelonLogger.Msg("Created lobby " + lobby.Id); SyncLogger.Msg("Created lobby " + lobby.Id);
UpdateLobbyData(); UpdateLobbyData();
_lobbyInstance.SetPublic();
}; };
SteamMatchmaking.OnLobbyEntered += (Lobby lobby) => SteamMatchmaking.OnLobbyEntered += (Lobby lobby) =>
{ {
_lobbyInstance = lobby; _lobbyInstance = lobby;
MelonLogger.Msg("Entered lobby " + lobby.Id); SyncLogger.Msg("Entered lobby " + lobby.Id);
UpdateLobbyData(); UpdateLobbyData();
}; };
SteamMatchmaking.OnLobbyMemberLeave += (Lobby lobby, Friend friend) => SteamMatchmaking.OnLobbyMemberLeave += (Lobby lobby, Friend friend) =>
{ {
if (friend.Id == SteamClient.SteamId) if (friend.Id == SteamClient.SteamId)
{ {
MelonLogger.Msg("Left lobby " + lobby.Id); SyncLogger.Msg("Left lobby " + lobby.Id);
_lobbyInstance = new Lobby(); _lobbyInstance = new Lobby();
} }
MelonLogger.Msg("Member left " + friend.Id); SyncLogger.Msg("Member left " + friend.Id);
UpdateLobbyData(); UpdateLobbyData();
}; };
SteamMatchmaking.OnLobbyMemberJoined += (Lobby lobby, Friend friend) => SteamMatchmaking.OnLobbyMemberJoined += (Lobby lobby, Friend friend) =>
{ {
MelonLogger.Msg("Member joined " + friend.Id); SyncLogger.Msg("Member joined " + friend.Id);
SteamFriends.SetPlayedWith(friend.Id);
UpdateLobbyData(); UpdateLobbyData();
}; };
MelonLogger.Msg("SteamLobbyManager initialized"); SyncLogger.Msg("SteamLobbyManager initialized");
SteamFriends.OnGameLobbyJoinRequested += (Lobby lobby, SteamId friend) => SteamFriends.OnGameLobbyJoinRequested += (Lobby lobby, SteamId friend) =>
{ {
MelonLogger.Msg("Joining lobby " + lobby.Id); SyncLogger.Msg("Joining lobby " + lobby.Id);
JoinLobby(lobby.Id.Value); JoinLobby(lobby.Id.Value);
}; };
SteamFriends.OnGameRichPresenceJoinRequested += (Friend friend, string connectString) => SteamFriends.OnGameRichPresenceJoinRequested += (Friend friend, string connectString) =>
{ {
MelonLogger.Msg("Joining lobby " + connectString); SyncLogger.Msg("Joining lobby " + connectString);
ulong lobbyId = ulong.Parse(connectString.Split(':')[1]); ulong lobbyId = ulong.Parse(connectString.Split(':')[1]);
JoinLobby(lobbyId); JoinLobby(lobbyId);
}; };
SteamNetworkingUtils.InitRelayNetworkAccess();
//SteamMatchmaking.LobbyList.
} }
private Lobby _lobbyInstance; private Lobby _lobbyInstance;
@@ -65,10 +70,16 @@ namespace BoneSync.Networking.LobbyManager
get; get;
private set; private set;
} }
private SteamId[] _steamIds;
public SteamId[] steamIds public SteamId[] steamIds
{ {
get; get => _steamIds;
private set; private set
{
_steamIds = value;
_peersCache = _steamIds.Select(x => x.Value).ToArray();
}
} }
public override bool IsConnected() public override bool IsConnected()
@@ -76,7 +87,8 @@ namespace BoneSync.Networking.LobbyManager
return _lobbyInstance.Id.IsValid; return _lobbyInstance.Id.IsValid;
} }
public override ulong[] GetPeers() => steamIds.Select(x => x.Value).ToArray(); private ulong[] _peersCache = new ulong[0];
public override ulong[] GetPeers() => _peersCache;
public override ulong GetLocalId() => SteamClient.SteamId.Value; public override ulong GetLocalId() => SteamClient.SteamId.Value;
public override ulong GetHostId() => _lobbyInstance.Owner.Id.Value; public override ulong GetHostId() => _lobbyInstance.Owner.Id.Value;
public override ulong GetLobbyId() => _lobbyInstance.Id.Value; public override ulong GetLobbyId() => _lobbyInstance.Id.Value;
@@ -86,6 +98,8 @@ namespace BoneSync.Networking.LobbyManager
LobbyMembers = _lobbyInstance.Members.ToArray(); LobbyMembers = _lobbyInstance.Members.ToArray();
steamIds = LobbyMembers.Select(x => x.Id).ToArray(); steamIds = LobbyMembers.Select(x => x.Id).ToArray();
if (_lobbyInstance.Id.IsValid) if (_lobbyInstance.Id.IsValid)
{ {
SteamFriends.SetRichPresence("steam_display", "BoneSync - " + SceneSync.CurrentSceneDisplayName); SteamFriends.SetRichPresence("steam_display", "BoneSync - " + SceneSync.CurrentSceneDisplayName);
@@ -103,12 +117,14 @@ namespace BoneSync.Networking.LobbyManager
SteamFriends.SetRichPresence("status", "Not in a multiplayer lobby"); SteamFriends.SetRichPresence("status", "Not in a multiplayer lobby");
} }
_lobbyInstance.SetMemberData("sceneName", SceneSync.CurrentSceneDisplayName);
_lobbyInstance.SetMemberData("playerModel", "");
} }
public override void CreateLobby() public override void CreateLobby()
{ {
LeaveLobby(); LeaveLobby();
MelonLogger.Msg("Trying to create lobby"); SyncLogger.Msg("Trying to create lobby");
_ = SteamMatchmaking.CreateLobbyAsync(16); _ = SteamMatchmaking.CreateLobbyAsync(16);
} }
@@ -116,7 +132,7 @@ namespace BoneSync.Networking.LobbyManager
public override void JoinLobby(ulong lobbyId) public override void JoinLobby(ulong lobbyId)
{ {
LeaveLobby(); LeaveLobby();
MelonLogger.Msg("Trying to join lobby " + lobbyId); SyncLogger.Msg("Trying to join lobby " + lobbyId);
_ = SteamMatchmaking.JoinLobbyAsync(lobbyId); _ = SteamMatchmaking.JoinLobbyAsync(lobbyId);
} }

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using PuppetMasta;
using StressLevelZero.AI;
namespace BoneSync.Networking.Messages
{
public struct AIBehaviourHealth
{
public float cur_hp;
public float cur_arm_lf;
public float cur_arm_rt;
public float cur_leg_lf;
public float cur_leg_rt;
public AIBehaviourHealth(SubBehaviourHealth behaviourHealth)
{
cur_hp = behaviourHealth.cur_hp;
cur_arm_lf = behaviourHealth.cur_arm_lf;
cur_arm_rt = behaviourHealth.cur_arm_rt;
cur_leg_lf = behaviourHealth.cur_leg_lf;
cur_leg_rt = behaviourHealth.cur_leg_rt;
}
}
public struct AISyncInfo
{
public ushort syncId;
public AIBehaviourHealth health;
public PuppetMaster.State puppetState;
public PuppetMaster.Mode puppetMode;
public BehaviourBaseNav.MentalState mentalState;
public AISyncInfo(ushort syncid, AIBrain aiBrain)
{
this.syncId = syncid;
this.health = new AIBehaviourHealth(aiBrain.behaviour.health);
this.puppetState = aiBrain.puppetMaster.activeState;
this.puppetMode = aiBrain.puppetMaster.activeMode;
this.mentalState = aiBrain.behaviour.mentalState;
}
}
[PacketType(PacketType.AISync), PacketReliability(PacketReliability.Unreliable)]
public class AISyncMessage : NetworkMessage
{
private AISyncInfo _aiSyncInfo;
public AISyncInfo aiSyncInfo => _aiSyncInfo;
public AISyncMessage(AISyncInfo aiSyncInfo)
{
_aiSyncInfo = aiSyncInfo;
byteEncoder.WriteUShort(_aiSyncInfo.syncId);
byteEncoder.WriteCompressedFloat(_aiSyncInfo.health.cur_hp);
byteEncoder.WriteCompressedFloat(_aiSyncInfo.health.cur_arm_lf);
byteEncoder.WriteCompressedFloat(_aiSyncInfo.health.cur_arm_rt);
byteEncoder.WriteCompressedFloat(_aiSyncInfo.health.cur_leg_lf);
byteEncoder.WriteCompressedFloat(_aiSyncInfo.health.cur_leg_rt);
byteEncoder.WriteByte((byte)_aiSyncInfo.puppetState);
byteEncoder.WriteByte((byte)_aiSyncInfo.puppetMode);
byteEncoder.WriteByte((byte)_aiSyncInfo.mentalState);
}
public AISyncMessage(Packet packet)
{
byteEncoder.WriteBytes(packet.Data);
_aiSyncInfo = new AISyncInfo();
_aiSyncInfo.syncId = byteEncoder.ReadUShort();
_aiSyncInfo.health.cur_hp = byteEncoder.ReadCompressedFloat();
_aiSyncInfo.health.cur_arm_lf = byteEncoder.ReadCompressedFloat();
_aiSyncInfo.health.cur_arm_rt = byteEncoder.ReadCompressedFloat();
_aiSyncInfo.health.cur_leg_lf = byteEncoder.ReadCompressedFloat();
_aiSyncInfo.health.cur_leg_rt = byteEncoder.ReadCompressedFloat();
_aiSyncInfo.puppetState = (PuppetMaster.State)byteEncoder.ReadByte();
_aiSyncInfo.puppetMode = (PuppetMaster.Mode)byteEncoder.ReadByte();
_aiSyncInfo.mentalState = (BehaviourBaseNav.MentalState)byteEncoder.ReadByte();
}
public override void Execute()
{
Syncable syncable = ObjectSyncCache.GetSyncable(_aiSyncInfo.syncId);
if (syncable == null) return;
syncable.OnAISyncData(_aiSyncInfo);
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using MelonLoader;
namespace BoneSync.Networking.Messages
{
public struct DiscardSyncableMessageData
{
public ushort syncId;
public bool despawn;
}
[PacketType(PacketType.DiscardSyncable), PacketReliability(PacketReliability.ReliableFast)]
internal class DiscardSyncableMessage : NetworkMessage
{
private DiscardSyncableMessageData _data;
public DiscardSyncableMessage(DiscardSyncableMessageData discardSyncableMessageData)
{
_data = discardSyncableMessageData;
byteEncoder.WriteUShort(_data.syncId);
byteEncoder.WriteBool(_data.despawn);
}
public DiscardSyncableMessage(Packet packet)
{
byteEncoder.WriteBytes(packet.Data);
_data.syncId = byteEncoder.ReadUShort();
_data.despawn = byteEncoder.ReadBool();
}
public override void Execute()
{
Syncable syncable = ObjectSyncCache.GetSyncable(_data.syncId);
if (syncable != null)
{
syncable.DiscardSyncable(true, _data.despawn);
} else
{
SyncLogger.Warning(senderId + " tried to discard a syncable that doesn't exist (syncId: " + _data.syncId + ")");
}
}
}
}

View File

@@ -0,0 +1,68 @@
using BoneSync.Sync;
using BoneSync.Sync.Components;
using MelonLoader;
using StressLevelZero.Combat;
using StressLevelZero.Props.Weapons;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BoneSync.Networking.Messages
{
public enum GunSyncMessageType
{
StateUpdate = 0,
Fire = 1,
EjectCartridge = 2,
}
public struct GunSyncInfo
{
public ushort syncId;
public BulletObject bulletObject;
public GunSyncMessageType messageType;
public Gun.HammerStates hammerState;
public Gun.CartridgeStates cartridgeState;
}
[PacketType(PacketType.GunSync), PacketReliability(PacketReliability.Unreliable)]
public class GunSyncMessage : NetworkMessage
{
GunSyncInfo gunSyncInfo;
public GunSyncMessage(GunSyncInfo gunSyncInfo)
{
this.gunSyncInfo = gunSyncInfo;
byteEncoder.WriteUShort(gunSyncInfo.syncId);
byteEncoder.WriteByte((byte)gunSyncInfo.messageType);
byteEncoder.WriteByte((byte)gunSyncInfo.hammerState);
byteEncoder.WriteByte((byte)gunSyncInfo.cartridgeState);
byteEncoder.WriteBool(gunSyncInfo.bulletObject != null);
byteEncoder.WriteAmmoVariables(gunSyncInfo.bulletObject.ammoVariables);
}
public GunSyncMessage(Packet packet) {
byteEncoder.WriteBytes(packet.Data);
gunSyncInfo.syncId = byteEncoder.ReadUShort();
gunSyncInfo.messageType = (GunSyncMessageType)byteEncoder.ReadByte();
gunSyncInfo.hammerState = (Gun.HammerStates)byteEncoder.ReadByte();
gunSyncInfo.cartridgeState = (Gun.CartridgeStates)byteEncoder.ReadByte();
if (byteEncoder.ReadBool()) { // If bulletObject is not null
gunSyncInfo.bulletObject = new BulletObject() { ammoVariables = byteEncoder.ReadAmmoVariables() };
}
}
public override void Execute()
{
Syncable syncable = ObjectSyncCache.GetSyncable(gunSyncInfo.syncId);
if (syncable == null) return;
//SyncLogger.Msg("GunSyncMessage.Execute: " + gunSyncInfo.syncId + ":" + syncable.name + " hasBulletObject: " + (gunSyncInfo.bulletObject != null));
syncable.OnWeaponSyncData(gunSyncInfo);
}
}
}

View File

@@ -3,7 +3,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
#if TEST
using Xunit; using Xunit;
#endif
namespace BoneSync.Networking.Messages namespace BoneSync.Networking.Messages
{ {
@@ -40,7 +43,7 @@ namespace BoneSync.Networking.Messages
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
#if TEST
public class LobbyInfoMessageTests public class LobbyInfoMessageTests
{ {
[Fact] [Fact]
@@ -58,4 +61,5 @@ namespace BoneSync.Networking.Messages
Assert.Equal(5, newMessage.CurrentPlayers); Assert.Equal(5, newMessage.CurrentPlayers);
} }
} }
#endif
} }

View File

@@ -0,0 +1,43 @@
using BoneSync.Sync;
using BoneSync.Sync.Components;
using StressLevelZero.Combat;
using StressLevelZero.Props.Weapons;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BoneSync.Networking.Messages
{
public struct MagazineSyncData
{
public ushort syncId;
public MagazineData magazineData;
}
[PacketType(PacketType.MagazineSync), PacketReliability(PacketReliability.Unreliable)]
public class MagazineSyncMessage : NetworkMessage
{
public MagazineSyncData magazineData;
public MagazineSyncMessage(MagazineSyncData magazineData)
{
this.magazineData = magazineData;
byteEncoder.WriteMagazineData(magazineData.magazineData);
}
public MagazineSyncMessage(Packet packet)
{
byteEncoder.WriteBytes(packet.Data);
magazineData = new MagazineSyncData();
}
public override void Execute()
{
Syncable syncable = ObjectSyncCache.GetSyncable(magazineData.syncId);
if (!syncable) return;
syncable.ApplyMagazineData(magazineData);
}
}
}

View File

@@ -0,0 +1,87 @@
using BoneSync.Sync;
using StressLevelZero.Combat;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace BoneSync.Networking.Messages
{
public struct ObjectHealthInfo
{
public float _health;
public int _hits;
public Vector3 normal;
public float damage;
public bool crit;
public AttackType attackType;
public byte index;
public ObjectHealthInfo(float health, int hits, Vector3 normal, float damage, bool crit, AttackType attackType, byte index)
{
_health = health;
_hits = hits;
this.normal = normal;
this.damage = damage;
this.crit = crit;
this.attackType = attackType;
this.index = index;
}
}
public enum ObjectDamageType
{
Unknown = 0,
SyncHealth = 1,
DestructibleTakeDamage = 2,
PropHealthTakeDamage = 3,
}
public struct ObjectDamageInfo
{
public ushort objectId;
public ObjectDamageType eventType;
public ObjectHealthInfo objectHealthInfo;
}
[PacketType(PacketType.ObjectDamageEvent), PacketReliability(PacketReliability.Unreliable), PacketCatchup(4)]
public class ObjectDamageMessage : NetworkMessage
{
public ObjectDamageInfo objectEventInfo => _objectEventInfo;
private ObjectDamageInfo _objectEventInfo;
public ObjectDamageMessage(ObjectDamageInfo objectEventInfo)
{
_objectEventInfo = objectEventInfo;
byteEncoder.WriteUShort(_objectEventInfo.objectId);
byteEncoder.WriteByte((byte)_objectEventInfo.eventType);
byteEncoder.WriteByte(_objectEventInfo.objectHealthInfo.index);
byteEncoder.WriteFloat(_objectEventInfo.objectHealthInfo._health);
byteEncoder.WriteInt(_objectEventInfo.objectHealthInfo._hits);
if (_objectEventInfo.eventType == ObjectDamageType.SyncHealth) return; // No need to write the rest of the data
byteEncoder.WriteVector3(_objectEventInfo.objectHealthInfo.normal);
byteEncoder.WriteFloat(_objectEventInfo.objectHealthInfo.damage);
byteEncoder.WriteBool(_objectEventInfo.objectHealthInfo.crit);
byteEncoder.WriteByte((byte)_objectEventInfo.objectHealthInfo.attackType);
}
public ObjectDamageMessage(Packet packet)
{
byteEncoder.WriteBytes(packet.Data);
_objectEventInfo.objectId = byteEncoder.ReadUShort();
_objectEventInfo.eventType = (ObjectDamageType)byteEncoder.ReadByte();
_objectEventInfo.objectHealthInfo.index = byteEncoder.ReadByte();
_objectEventInfo.objectHealthInfo._health = byteEncoder.ReadFloat();
_objectEventInfo.objectHealthInfo._hits = byteEncoder.ReadInt();
if (_objectEventInfo.eventType == ObjectDamageType.SyncHealth) return; // No need to read the rest of the data
_objectEventInfo.objectHealthInfo.normal = byteEncoder.ReadVector3();
_objectEventInfo.objectHealthInfo.damage = byteEncoder.ReadFloat();
_objectEventInfo.objectHealthInfo.crit = byteEncoder.ReadBool();
_objectEventInfo.objectHealthInfo.attackType = (AttackType)byteEncoder.ReadByte();
}
public override void Execute()
{
ObjectSync.OnObjectDamageMessage(this);
}
}
}

View File

@@ -1,4 +1,5 @@
using BoneSync.Sync; using BoneSync.Data;
using BoneSync.Sync;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -11,8 +12,9 @@ namespace BoneSync.Networking.Messages
public struct ObjectSyncTransform public struct ObjectSyncTransform
{ {
public SimpleTransform transform; public SimpleSyncTransform transform;
public Vector3 velocity; public Vector3 velocity;
public Vector3 angularVelocity;
} }
public struct ObjectSyncMessageData public struct ObjectSyncMessageData
{ {
@@ -20,7 +22,7 @@ namespace BoneSync.Networking.Messages
public ObjectSyncTransform[] objectSyncTransforms; public ObjectSyncTransform[] objectSyncTransforms;
} }
[PacketType(PacketType.ObjectSync), PacketReliability(PacketReliability.UnreliableNoDelay)] [PacketType(PacketType.ObjectSync), PacketReliability(PacketReliability.Unreliable)]
internal class ObjectSyncMessage : NetworkMessage internal class ObjectSyncMessage : NetworkMessage
{ {
private ObjectSyncMessageData _objectSyncMessageData = new ObjectSyncMessageData(); private ObjectSyncMessageData _objectSyncMessageData = new ObjectSyncMessageData();

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Sync;
namespace BoneSync.Networking.Messages
{
public struct OwnershipTransferMessageData
{
public ushort syncId;
public bool force;
public ulong NewOwnerId;
}
[PacketType(PacketType.ObjectOwnership), PacketReliability(PacketReliability.ReliableFast), PacketCatchup(2)]
internal class OwnershipTransferMessage : NetworkMessage
{
private OwnershipTransferMessageData Data;
public OwnershipTransferMessage(OwnershipTransferMessageData data)
{
Data = data;
byteEncoder.WriteUShort(Data.syncId);
byteEncoder.WriteULong(Data.NewOwnerId);
byteEncoder.WriteBool(Data.force);
}
public OwnershipTransferMessage(Packet packet)
{
byteEncoder.WriteBytes(packet.Data);
Data.syncId = byteEncoder.ReadUShort();
Data.NewOwnerId = byteEncoder.ReadULong();
Data.force = byteEncoder.ReadBool();
}
override public void Execute()
{
ObjectSync.OnOwnershipChangeMessage(Data.syncId, Data.NewOwnerId, Data.force);
}
}
}

View File

@@ -1,23 +1,49 @@
 
using BoneSync.Data;
using BoneSync.Player;
using BoneSync.Sync; using BoneSync.Sync;
using StressLevelZero.Player;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine;
namespace BoneSync.Networking.Messages namespace BoneSync.Networking.Messages
{ {
public struct SimpleFingerCurl
{
public float thumb;
public float index;
public float middle;
public float ring;
public float pinky;
public SimpleFingerCurl(FingerCurl fingerCurl)
{
thumb = fingerCurl.thumb;
index = fingerCurl.index;
middle = fingerCurl.middle;
ring = fingerCurl.ring;
pinky = fingerCurl.pinky;
}
}
public struct PlayerSyncInfo public struct PlayerSyncInfo
{ {
public SimpleTransform headPos; public Vector3 rootPos;
public SimpleTransform leftHandPos; public SimpleSyncTransform headPos;
public SimpleTransform rightHandPos; public SimpleSyncTransform leftHandPos;
public SimpleSyncTransform rightHandPos;
public SimpleFingerCurl leftHandFingerCurl;
public SimpleFingerCurl rightHandFingerCurl;
public byte poseIndexRight;
public byte poseIndexLeft;
public float poseRadiusRight;
public float poseRadiusLeft;
} }
[PacketType(PacketType.PlayerSync)] [PacketType(PacketType.PlayerSync), PacketReliability(PacketReliability.Unreliable)]
internal class PlayerSyncMessage : NetworkMessage internal class PlayerSyncMessage : NetworkMessage
{ {
private PlayerSyncInfo _playerSyncInfo; private PlayerSyncInfo _playerSyncInfo;
@@ -26,22 +52,36 @@ namespace BoneSync.Networking.Messages
public PlayerSyncMessage(PlayerSyncInfo playerSyncInfo) public PlayerSyncMessage(PlayerSyncInfo playerSyncInfo)
{ {
_playerSyncInfo = playerSyncInfo; _playerSyncInfo = playerSyncInfo;
byteEncoder.WriteVector3(_playerSyncInfo.rootPos);
byteEncoder.WriteSimpleTransform(_playerSyncInfo.headPos); byteEncoder.WriteSimpleTransform(_playerSyncInfo.headPos);
byteEncoder.WriteSimpleTransform(_playerSyncInfo.leftHandPos); byteEncoder.WriteSimpleTransform(_playerSyncInfo.leftHandPos);
byteEncoder.WriteSimpleTransform(_playerSyncInfo.rightHandPos); byteEncoder.WriteSimpleTransform(_playerSyncInfo.rightHandPos);
byteEncoder.WriteByte(_playerSyncInfo.poseIndexRight);
byteEncoder.WriteByte(_playerSyncInfo.poseIndexLeft);
byteEncoder.WriteCompressedFloat(_playerSyncInfo.poseRadiusRight);
byteEncoder.WriteCompressedFloat(_playerSyncInfo.poseRadiusLeft);
byteEncoder.WriteFingerCurl(_playerSyncInfo.leftHandFingerCurl);
byteEncoder.WriteFingerCurl(_playerSyncInfo.rightHandFingerCurl);
} }
public PlayerSyncMessage(Packet packet) public PlayerSyncMessage(Packet packet)
{ {
byteEncoder.WriteBytes(packet.Data); byteEncoder.WriteBytes(packet.Data);
_playerSyncInfo.rootPos = byteEncoder.ReadVector3();
_playerSyncInfo.headPos = byteEncoder.ReadSimpleTransform(); _playerSyncInfo.headPos = byteEncoder.ReadSimpleTransform();
_playerSyncInfo.leftHandPos = byteEncoder.ReadSimpleTransform(); _playerSyncInfo.leftHandPos = byteEncoder.ReadSimpleTransform();
_playerSyncInfo.rightHandPos = byteEncoder.ReadSimpleTransform(); _playerSyncInfo.rightHandPos = byteEncoder.ReadSimpleTransform();
_playerSyncInfo.poseIndexRight = byteEncoder.ReadByte();
_playerSyncInfo.poseIndexLeft = byteEncoder.ReadByte();
_playerSyncInfo.poseRadiusRight = byteEncoder.ReadCompressedFloat();
_playerSyncInfo.poseRadiusLeft = byteEncoder.ReadCompressedFloat();
_playerSyncInfo.leftHandFingerCurl = byteEncoder.ReadFingerCurl();
_playerSyncInfo.rightHandFingerCurl = byteEncoder.ReadFingerCurl();
} }
public override void Execute() public override void Execute()
{ {
PlayerSync.OnPlayerSync(this); PlayerRig.OnPlayerSync(this);
} }
} }
} }

View File

@@ -0,0 +1,86 @@
using BoneSync.Data;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using MelonLoader;
using StressLevelZero.Interaction;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BoneSync.Networking.Messages
{
public struct PlugSyncMessageData
{
public ushort plugSyncId;
public byte plugIndex;
public ushort socketSyncId;
public byte socketIndex;
}
[PacketType(PacketType.PlugSync), PacketReliability(PacketReliability.Unreliable), PacketCatchup(10)]
internal class PlugSyncMessage : NetworkMessage
{
private PlugSyncMessageData messageData;
public PlugSyncMessage(PlugSyncMessageData plugSyncMessageData)
{
this.messageData = plugSyncMessageData;
byteEncoder.WriteUShort(plugSyncMessageData.plugSyncId);
byteEncoder.WriteByte(plugSyncMessageData.plugIndex);
byteEncoder.WriteUShort(plugSyncMessageData.socketSyncId);
byteEncoder.WriteByte(plugSyncMessageData.socketIndex);
}
public PlugSyncMessage(Packet packet) {
byteEncoder.WriteBytes(packet.Data);
messageData.plugSyncId = byteEncoder.ReadUShort();
messageData.plugIndex = byteEncoder.ReadByte();
messageData.socketSyncId = byteEncoder.ReadUShort();
messageData.socketIndex = byteEncoder.ReadByte();
}
public override void Execute()
{
//SyncLogger.Msg("Got PlugSyncMessage " + messageData.plugSyncId + ":" + messageData.plugIndex + " " + messageData.socketSyncId + ":" + messageData.socketIndex);
Syncable plugSyncable = ObjectSyncCache.GetSyncable(messageData.plugSyncId);
Syncable socketSyncable = ObjectSyncCache.GetSyncable(messageData.socketSyncId);
if (!plugSyncable || messageData.plugIndex == byte.MaxValue)
{
SyncLogger.Warning("PlugSyncable not found");
return;
}
AlignPlug plug = plugSyncable.GetPlugFromId(messageData.plugIndex);
if (plug == null) {
SyncLogger.Warning("Plug not found, " + plugSyncable.transform.GetPath());
return;
}
if (plug == null) {
SyncLogger.Warning("Plug is not an AlignPlug");
return;
}
if (!socketSyncable || messageData.socketIndex == byte.MaxValue) {
SyncLogger.Warning("SocketSyncable not found");
plug.SafeEject();
return;
}
Socket socket = socketSyncable.GetSocketFromId(messageData.socketIndex);
if (socket == null)
{
SyncLogger.Warning("Unable to find socket from ID");
plug.SafeEject();
return;
}
//SyncLogger.Msg("Inserting networked plug");
plug.SafeInsert(socket);
}
}
}

View File

@@ -1,4 +1,6 @@
using BoneSync.Sync; using BoneSync.Data;
using BoneSync.Patching;
using BoneSync.Sync;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -12,15 +14,23 @@ namespace BoneSync.Networking.Messages
RegisterFromPath = 0, RegisterFromPath = 0,
RegisterAndSpawn = 1, RegisterAndSpawn = 1,
} }
public struct SpawnPoolableInfo
{
public string spawnableTitle;
public SimpleSyncTransform spawnLocation;
}
public struct RegisterSyncableInfo public struct RegisterSyncableInfo
{ {
public string transformPath; public string transformPath;
public ushort id; public ushort id;
public ushort callbackId;
public ulong ownerId; public ulong ownerId;
public RegisterSyncType type; public RegisterSyncType type;
public SpawnPoolableInfo? spawnInfo;
} }
[PacketType(PacketType.RegisterSyncable)] [PacketType(PacketType.RegisterSyncable), PacketReliability(PacketReliability.ReliableFast), PacketCatchup(1)]
internal class RegisterSyncableMessage : NetworkMessage internal class RegisterSyncableMessage : NetworkMessage
{ {
private RegisterSyncableInfo _info; private RegisterSyncableInfo _info;
@@ -29,9 +39,19 @@ namespace BoneSync.Networking.Messages
{ {
_info = info; _info = info;
byteEncoder.WriteByte((byte)_info.type); byteEncoder.WriteByte((byte)_info.type);
byteEncoder.WriteString(_info.transformPath); byteEncoder.WriteULong(_info.ownerId);
byteEncoder.WriteUlong(_info.ownerId);
byteEncoder.WriteUShort(_info.id); byteEncoder.WriteUShort(_info.id);
byteEncoder.WriteUShort(_info.callbackId);
switch (_info.type)
{
case RegisterSyncType.RegisterAndSpawn:
byteEncoder.WriteString(_info.spawnInfo.Value.spawnableTitle);
byteEncoder.WriteSimpleTransform(_info.spawnInfo.Value.spawnLocation);
break;
case RegisterSyncType.RegisterFromPath:
byteEncoder.WriteString(_info.transformPath);
break;
}
} }
@@ -39,9 +59,22 @@ namespace BoneSync.Networking.Messages
{ {
byteEncoder.WriteBytes(packet.Data); byteEncoder.WriteBytes(packet.Data);
_info.type = (RegisterSyncType)byteEncoder.ReadByte(); _info.type = (RegisterSyncType)byteEncoder.ReadByte();
_info.transformPath = byteEncoder.ReadString(); _info.ownerId = byteEncoder.ReadULong();
_info.ownerId = byteEncoder.ReadUlong();
_info.id = byteEncoder.ReadUShort(); _info.id = byteEncoder.ReadUShort();
_info.callbackId = byteEncoder.ReadUShort();
switch (_info.type)
{
case RegisterSyncType.RegisterAndSpawn:
_info.spawnInfo = new SpawnPoolableInfo()
{
spawnableTitle = byteEncoder.ReadString(),
spawnLocation = byteEncoder.ReadSimpleTransform(),
};
break;
case RegisterSyncType.RegisterFromPath:
_info.transformPath = byteEncoder.ReadString();
break;
}
} }
public override void Execute() public override void Execute()

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Sync;
using MelonLoader;
using StressLevelZero.Utilities;
namespace BoneSync.Networking.Messages
{
public enum sceneChangeType
{
FromIndex,
FromName,
CustomMap,
}
public struct SceneChangeInfo
{
public sceneChangeType sceneChangeType;
public string sceneName;
public byte sceneIndex;
public bool reloadScene;
}
[PacketType(PacketType.SceneChange), PacketReliability(PacketReliability.Reliable), AlwaysExecute]
internal class SceneChangeMessage : NetworkMessage
{
private SceneChangeInfo _sceneChangeInfo;
public SceneChangeInfo sceneChangeInfo => _sceneChangeInfo;
public SceneChangeMessage(SceneChangeInfo sceneChangeInfo)
{
_sceneChangeInfo = sceneChangeInfo;
byteEncoder.WriteByte((byte)_sceneChangeInfo.sceneChangeType);
byteEncoder.WriteBool(_sceneChangeInfo.reloadScene);
switch (_sceneChangeInfo.sceneChangeType)
{
case sceneChangeType.FromIndex:
byteEncoder.WriteByte(_sceneChangeInfo.sceneIndex);
break;
case sceneChangeType.FromName:
byteEncoder.WriteString(_sceneChangeInfo.sceneName);
break;
case sceneChangeType.CustomMap:
byteEncoder.WriteString(_sceneChangeInfo.sceneName);
break;
}
}
public SceneChangeMessage(Packet packet)
{
byteEncoder.SetBytes(packet.Data);
_sceneChangeInfo = new SceneChangeInfo();
_sceneChangeInfo.sceneChangeType = (sceneChangeType)byteEncoder.ReadByte();
_sceneChangeInfo.reloadScene = byteEncoder.ReadBool();
switch (_sceneChangeInfo.sceneChangeType)
{
case sceneChangeType.FromIndex:
_sceneChangeInfo.sceneIndex = byteEncoder.ReadByte();
break;
case sceneChangeType.FromName:
_sceneChangeInfo.sceneName = byteEncoder.ReadString();
break;
case sceneChangeType.CustomMap:
_sceneChangeInfo.sceneName = byteEncoder.ReadString();
break;
}
}
public override void Execute()
{
SyncLogger.Msg("SceneChangeMessage: " + _sceneChangeInfo.sceneName + " " + _sceneChangeInfo.sceneIndex);
switch (_sceneChangeInfo.sceneChangeType)
{
case sceneChangeType.FromIndex:
if (!_sceneChangeInfo.reloadScene && _sceneChangeInfo.sceneIndex == BoneworksSceneManager.currentSceneIndex) return; // don't reload the scene if it's already loaded
BoneworksSceneManager.LoadScene(_sceneChangeInfo.sceneIndex);
break;
case sceneChangeType.FromName:
BoneworksSceneManager.LoadScene(_sceneChangeInfo.sceneName);
break;
case sceneChangeType.CustomMap:
BoneworksSceneManager.LoadScene(_sceneChangeInfo.sceneName);
break;
}
}
}
}

View File

@@ -0,0 +1,70 @@
using BoneSync.Sync;
using BoneSync.Sync.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BoneSync.Networking.Messages
{
public enum SimpleEventType
{
None = 0,
OnDevicePull = 1,
OnDeviceRelease = 2,
OnButtonPress = 3,
OnButtonRelease = 4,
OnButtonOneShot = 5,
CartGo = 6,
CartGoBackwards = 7,
CartLaunch = 8,
CartGoForward = 9,
CartDrop = 10,
}
public struct SimpleSyncableEvent
{
public ushort syncId;
public SimpleEventType eventType;
public byte index;
public byte length;
public byte[] extraData;
}
[PacketType(PacketType.SimpleObjectEventSync), PacketReliability(PacketReliability.ReliableFast), PacketCatchup(20)]
public class SimpleSyncableEventMessage : NetworkMessage
{
private SimpleSyncableEvent eventData;
public SimpleSyncableEventMessage(SimpleSyncableEvent simpleSyncableEvent)
{
eventData = simpleSyncableEvent;
byteEncoder.WriteUShort(simpleSyncableEvent.syncId);
byteEncoder.WriteByte((byte)simpleSyncableEvent.eventType);
byteEncoder.WriteByte(simpleSyncableEvent.index);
byteEncoder.WriteByte(simpleSyncableEvent.length);
if (simpleSyncableEvent.extraData == null)
{
simpleSyncableEvent.extraData = new byte[0];
}
byteEncoder.WriteByte((byte)simpleSyncableEvent.extraData.Length);
byteEncoder.WriteBytes(simpleSyncableEvent.extraData);
}
public SimpleSyncableEventMessage(Packet packet)
{
byteEncoder.WriteBytes(packet.Data);
eventData.syncId = byteEncoder.ReadUShort();
eventData.eventType = (SimpleEventType)byteEncoder.ReadByte();
eventData.index = byteEncoder.ReadByte();
eventData.length = byteEncoder.ReadByte();
ushort extraDataLength = byteEncoder.ReadByte();
eventData.extraData = byteEncoder.ReadBytes(extraDataLength);
}
public override void Execute()
{
Syncable syncable = ObjectSyncCache.GetSyncable(eventData.syncId);
if (syncable == null) return;
syncable.OnSimpleSyncableEvent(eventData);
}
}
}

View File

@@ -1,13 +1,18 @@
using BoneSync.Networking.Transport; using System;
using MelonLoader;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Networking.Transport;
using BoneSync.Sync;
using MelonLoader;
using Oculus.Platform;
#if TEST
using Xunit; using Xunit;
#endif
namespace BoneSync.Networking namespace BoneSync.Networking
{ {
@@ -31,6 +36,20 @@ namespace BoneSync.Networking
public PacketReliability reliability { get; } public PacketReliability reliability { get; }
} }
public class PacketCatchupAttribute : Attribute
{
public int order { get; }
public PacketCatchupAttribute(int order)
{
this.order = order;
}
}
public class AlwaysExecuteAttribute : Attribute
{
public AlwaysExecuteAttribute() {}
}
public abstract class NetworkMessage public abstract class NetworkMessage
{ {
public ulong senderId public ulong senderId
@@ -44,10 +63,19 @@ namespace BoneSync.Networking
internal PacketType _packetType; internal PacketType _packetType;
internal PacketReliability? _reliability; internal PacketReliability? _reliability;
internal int? _catchupOrder;
internal bool? _alwaysExecute;
internal ByteEncoder byteEncoder = new ByteEncoder(); internal ByteEncoder byteEncoder = new ByteEncoder();
public byte[] GetBytes() => byteEncoder.ToArray(); public byte[] GetBytes() => byteEncoder.ToArray();
public bool GetAlwaysExecute()
{
if (_alwaysExecute.HasValue) return _alwaysExecute.Value;
AlwaysExecuteAttribute alwaysExecuteAttribute = GetType().GetCustomAttribute<AlwaysExecuteAttribute>();
_alwaysExecute = alwaysExecuteAttribute != null;
return _alwaysExecute.Value;
}
public PacketType GetPacketType() public PacketType GetPacketType()
{ {
RegisterPacketTypes(); RegisterPacketTypes();
@@ -64,6 +92,20 @@ namespace BoneSync.Networking
return _packetType; return _packetType;
} }
public int GetCatchupOrder()
{
if (_catchupOrder.HasValue) return _catchupOrder.Value;
PacketCatchupAttribute catchupAttribute = GetType().GetCustomAttribute<PacketCatchupAttribute>();
if (catchupAttribute == null)
{
_catchupOrder = -1;
return -1;
}
_catchupOrder = catchupAttribute.order;
return _catchupOrder.Value;
}
public PacketReliability GetPacketReliability() public PacketReliability GetPacketReliability()
{ {
if (_reliability.HasValue) return _reliability.Value; if (_reliability.HasValue) return _reliability.Value;
@@ -96,19 +138,34 @@ namespace BoneSync.Networking
} }
} }
} }
public static NetworkMessage ParsePacket(Packet packet)
private static readonly Dictionary<PacketType, ConstructorInfo> ConstructorCache = new Dictionary<PacketType, ConstructorInfo>();
public static ConstructorInfo GetConstructor(Packet packet)
{ {
MelonLogger.Msg("Received packet of type " + packet.Info.packetType + " from " + packet.Info.senderId + " Length: " + packet.Data.Length); if (ConstructorCache.ContainsKey(packet.Info.packetType))
// find a class that can parse this packet using Reflection {
// and return it return ConstructorCache[packet.Info.packetType];
}
if (!PacketTypeMap.ContainsKey(packet.Info.packetType)) if (!PacketTypeMap.ContainsKey(packet.Info.packetType))
{ {
throw new Exception("No class found for packet type '" + packet.Info.packetType+"'"); throw new Exception("No class found for packet type '" + packet.Info.packetType + "'");
} }
Type type = PacketTypeMap[packet.Info.packetType]; Type type = PacketTypeMap[packet.Info.packetType];
// get the constructor that takes a Packet // get the constructor that takes a Packet
ConstructorInfo constructor = type.GetConstructor(new Type[] { typeof(Packet) }) ?? throw new Exception("No constructor found for type " + type.Name); ConstructorInfo constructor = type.GetConstructor(new Type[] { typeof(Packet) }) ?? throw new Exception("No constructor found for type " + type.Name);
ConstructorCache[packet.Info.packetType] = constructor;
return constructor;
}
public static NetworkMessage ParsePacket(Packet packet)
{
//SyncLogger.Msg("Received packet of type " + packet.Info.packetType + " from " + packet.Info.senderId + " Length: " + packet.Data.Length);
// find a class that can parse this packet using Reflection
// and return it
ConstructorInfo constructor = GetConstructor(packet);
NetworkMessage networkMessage = (NetworkMessage)constructor.Invoke(new object[] { packet }); NetworkMessage networkMessage = (NetworkMessage)constructor.Invoke(new object[] { packet });
networkMessage.senderId = packet.Info.senderId; networkMessage.senderId = packet.Info.senderId;
return networkMessage; return networkMessage;
@@ -116,35 +173,40 @@ namespace BoneSync.Networking
public Packet GetPacket(int id, ulong senderId, ulong receiverId) public Packet GetPacket(ulong senderId, ulong receiverId)
{ {
PacketInfo packetInfo = new PacketInfo(id, senderId, receiverId, GetPacketType(), GetPacketReliability()); PacketInfo packetInfo = new PacketInfo(senderId, receiverId, GetPacketType(), GetPacketReliability(), 0, (byte)SceneSync.CurrentSceneIndex);
Packet packet = new Packet(packetInfo, GetBytes()); Packet packet = new Packet(packetInfo, GetBytes());
return packet; return packet;
} }
public void Broadcast() public void Broadcast()
{ {
Send(TransportBase.BORADCAST_ID); Send(BoneSync.transport.BROADCAST_ID);
}
public void SendToHost()
{
Send(BoneSync.lobby.GetHostId());
} }
public void Send(ulong receiverId) public void Send(ulong receiverId)
{ {
if (BoneSync.lobby.IsConnected() == false) if (BoneSync.IsConnected == false)
{ {
MelonLogger.Warning("Cannot send packet, not connected to lobby"); SyncLogger.Warning("Cannot send packet, not connected to lobby");
return; return;
} }
int PacketId = Packet.GenerateId();
ulong senderId = BoneSync.lobby.GetLocalId(); ulong senderId = BoneSync.lobby.GetLocalId();
Packet packet = GetPacket(PacketId, senderId, receiverId); Packet packet = GetPacket(senderId, receiverId);
BoneSync.transport.Send(packet); BoneSync.transport.Send(packet);
} }
public virtual void Execute() public virtual void Execute()
{ {
MelonLogger.Warning("Execute not implemented for " + GetType().Name); SyncLogger.Warning("Execute not implemented for " + GetType().Name);
} }
} }
#if TEST
public class NetworkMessageTests public class NetworkMessageTests
{ {
[Fact] [Fact]
@@ -154,4 +216,5 @@ namespace BoneSync.Networking
Assert.NotEmpty(NetworkMessage.PacketTypeMap); Assert.NotEmpty(NetworkMessage.PacketTypeMap);
} }
} }
#endif
} }

View File

@@ -1,11 +1,16 @@
using BoneSync.Networking.Messages; using BoneSync.Data;
using BoneSync.Networking.Messages;
using BoneSync.Sync;
using Facepunch.Steamworks; using Facepunch.Steamworks;
using MelonLoader;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
#if TEST
using Xunit; using Xunit;
#endif
namespace BoneSync.Networking namespace BoneSync.Networking
{ {
@@ -13,35 +18,65 @@ namespace BoneSync.Networking
public enum PacketReliability public enum PacketReliability
{ {
Unreliable = 0, Unreliable = 0,
UnreliableNoDelay = 1, Reliable = 1,
Reliable = 2, ReliableFast = 2,
ReliableWithBuffering = 3,
} }
public struct PacketInfo public struct PacketInfo
{ {
public int id; public ulong id;
public ulong senderId; public ulong senderId;
public ulong receiverId; public ulong receiverId;
public byte sceneIndex;
public PacketType packetType; public PacketType packetType;
public PacketReliability reliability; public PacketReliability reliability;
public PacketInfo( public PacketInfo(
int id,
ulong senderId, ulong senderId,
ulong receiverId, ulong receiverId,
PacketType packetType, PacketType packetType,
PacketReliability reliability = PacketReliability.Reliable PacketReliability reliability,
ulong id,
byte sceneIndex
) )
{ {
this.id = id;
this.senderId = senderId; this.senderId = senderId;
this.receiverId = receiverId; this.receiverId = receiverId;
this.packetType = packetType; this.packetType = packetType;
this.reliability = reliability; this.reliability = reliability;
this.id = id == 0 ? Packet.GenerateId() : id;
this.sceneIndex = sceneIndex;
} }
} }
public class Packet public class Packet
{ {
private static Dictionary<byte, Queue<NetworkMessage>> _packetQueues = new Dictionary<byte, Queue<NetworkMessage>>();
private static ulong _packetId = 1;
private static Dictionary<ulong, Dictionary<PacketType, ulong>> _latestPacketIds = new Dictionary<ulong, Dictionary<PacketType, ulong>>();
private static void SetLatestPacketId(ulong id, PacketType packetType, ulong value)
{
if (!_latestPacketIds.ContainsKey(id))
{
_latestPacketIds.Add(id, new Dictionary<PacketType, ulong>());
}
_latestPacketIds[id][packetType] = value;
}
private static ulong GetLatestPacketId(ulong id, PacketType packetType)
{
if (!_latestPacketIds.ContainsKey(id))
{
return 0;
}
if (!_latestPacketIds[id].ContainsKey(packetType))
{
return 0;
}
return _latestPacketIds[id][packetType];
}
public PacketInfo Info public PacketInfo Info
{ {
get => _packetInfo; get => _packetInfo;
@@ -64,11 +99,12 @@ namespace BoneSync.Networking
byte packetType = byteEncoder.ReadByte(); byte packetType = byteEncoder.ReadByte();
byte reliability = byteEncoder.ReadByte(); byte reliability = byteEncoder.ReadByte();
int id = byteEncoder.ReadInt(); byte sceneIndex = byteEncoder.ReadByte();
ulong senderId = byteEncoder.ReadUlong(); ulong id = byteEncoder.ReadULong();
ulong receiverId = byteEncoder.ReadUlong(); ulong senderId = byteEncoder.ReadULong();
ulong receiverId = byteEncoder.ReadULong();
PacketInfo packetInfo = new PacketInfo(id, senderId, receiverId, (PacketType)packetType, (PacketReliability)reliability); PacketInfo packetInfo = new PacketInfo(senderId, receiverId, (PacketType)packetType, (PacketReliability)reliability, id, sceneIndex);
return new Packet(packetInfo, byteEncoder.ToArray()); return new Packet(packetInfo, byteEncoder.ToArray());
} }
@@ -77,28 +113,83 @@ namespace BoneSync.Networking
ByteEncoder byteEncoder = new ByteEncoder(); ByteEncoder byteEncoder = new ByteEncoder();
byteEncoder.WriteByte((byte)_packetInfo.packetType); byteEncoder.WriteByte((byte)_packetInfo.packetType);
byteEncoder.WriteByte((byte)_packetInfo.reliability); byteEncoder.WriteByte((byte)_packetInfo.reliability);
byteEncoder.WriteInt(_packetInfo.id); byteEncoder.WriteByte(_packetInfo.sceneIndex);
byteEncoder.WriteUlong(_packetInfo.senderId); byteEncoder.WriteULong(_packetInfo.id);
byteEncoder.WriteUlong(_packetInfo.receiverId); byteEncoder.WriteULong(_packetInfo.senderId);
byteEncoder.WriteULong(_packetInfo.receiverId);
byteEncoder.WriteBytes(_dataBytes); byteEncoder.WriteBytes(_dataBytes);
return byteEncoder.ToArray(); return byteEncoder.ToArray();
} }
private static void EnsureQueueForScene(byte sceneIndex)
{
if (!_packetQueues.ContainsKey(sceneIndex))
{
_packetQueues.Add(sceneIndex, new Queue<NetworkMessage>());
}
}
public static bool OnPacketReceived(Packet packet) public static bool OnPacketReceived(Packet packet)
{ {
if (packet._packetInfo.reliability == PacketReliability.ReliableFast)
{
if (packet.Info.id <= GetLatestPacketId(packet.Info.senderId, packet.Info.packetType))
{
return false;
}
SetLatestPacketId(packet.Info.senderId, packet.Info.packetType, packet.Info.id);
}
NetworkMessage networkMessage = NetworkMessage.ParsePacket(packet); NetworkMessage networkMessage = NetworkMessage.ParsePacket(packet);
if (packet._packetInfo.sceneIndex != SceneSync.CurrentSceneIndex && !networkMessage.GetAlwaysExecute())
{
bool addToQueue = networkMessage.GetCatchupOrder() > 0;
if (!addToQueue) return false;
SyncLogger.Msg("Adding packet to queue for scene " + packet._packetInfo.sceneIndex);
EnsureQueueForScene(packet._packetInfo.sceneIndex);
_packetQueues[packet._packetInfo.sceneIndex].Enqueue(networkMessage);
return true;
}
networkMessage.Execute(); networkMessage.Execute();
return true; return true;
} }
public static void TryCatchup()
{
if (!BoneSync.IsConnected)
{
if (_packetQueues.Count > 0) _packetQueues.Clear();
return;
}
byte sceneIndex = (byte)SceneSync.CurrentSceneIndex;
if (!_packetQueues.ContainsKey(sceneIndex))
{
return;
}
Queue<NetworkMessage> queue = _packetQueues[sceneIndex];
int processed = 0;
while (queue.Count > 0 && processed < 10 && SceneSync.TimeSinceLastSceneChange > SceneSync.MAP_LOAD_GRACE_PERIOD)
{
processed++;
NetworkMessage networkMessage = queue.Dequeue();
networkMessage.Execute();
}
if (processed > 0)
{
SyncLogger.Msg("Processed " + processed + " queued packets for scene " + sceneIndex);
}
}
public byte[] Data => _dataBytes; public byte[] Data => _dataBytes;
public static int GenerateId() public static ulong GenerateId()
{ {
return new Random().Next(); return _packetId++;
} }
} }
#if TEST
public class PacketTests public class PacketTests
{ {
[Fact] [Fact]
@@ -127,4 +218,5 @@ namespace BoneSync.Networking
Assert.NotEqual(bytes, randomDataBytes); Assert.NotEqual(bytes, randomDataBytes);
} }
} }
#endif
} }

View File

@@ -13,5 +13,14 @@ namespace BoneSync.Networking
PlayerSync = 2, PlayerSync = 2,
RegisterSyncable = 3, RegisterSyncable = 3,
ObjectSync = 4, ObjectSync = 4,
ObjectDamageEvent = 5,
ObjectOwnership = 6,
DiscardSyncable = 7,
MagazineSync = 8,
GunSync = 9,
SimpleObjectEventSync = 10,
PlugSync = 11,
AISync = 12,
SceneChange = 13,
} }
} }

View File

@@ -4,24 +4,30 @@ using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Networking.LobbyManager; using BoneSync.Networking.LobbyManager;
using Facepunch.Steamworks; using Facepunch.Steamworks;
using Facepunch.Steamworks.Data; using Facepunch.Steamworks.Data;
using MelonLoader; using MelonLoader;
using Oculus.Platform;
namespace BoneSync.Networking.Transport namespace BoneSync.Networking.Transport
{ {
internal class SteamTransport : TransportBase internal class SteamTransport : ITransportBase
{ {
public SteamTransport() public SteamTransport()
{ {
SteamNetworking.OnP2PSessionRequest += OnP2PSessionRequest; SteamNetworking.OnP2PSessionRequest += OnP2PSessionRequest;
SteamNetworkingUtils.InitRelayNetworkAccess();
} }
private List<SteamId> OpenP2PConnections = new List<SteamId>(); private List<SteamId> OpenP2PConnections = new List<SteamId>();
public ulong BROADCAST_ID => 0;
private void OnP2PSessionRequest(SteamId steamId) private void OnP2PSessionRequest(SteamId steamId)
{ {
MelonLogger.Msg("P2P Request from " + steamId); SyncLogger.Msg("P2P Request from " + steamId);
if (BoneSync.lobby.GetPeers().Contains(steamId)) if (BoneSync.lobby.GetPeers().Contains(steamId))
{ {
SteamNetworking.AcceptP2PSessionWithUser(steamId); SteamNetworking.AcceptP2PSessionWithUser(steamId);
@@ -34,7 +40,7 @@ namespace BoneSync.Networking.Transport
} }
public override void CleanUp() public void CleanUp()
{ {
ulong[] peers = BoneSync.lobby.GetPeers(); ulong[] peers = BoneSync.lobby.GetPeers();
for (int i = 0; i < OpenP2PConnections.Count; i++) for (int i = 0; i < OpenP2PConnections.Count; i++)
@@ -51,16 +57,16 @@ namespace BoneSync.Networking.Transport
private void ProcessPacket(P2Packet steamPacket) private void ProcessPacket(P2Packet steamPacket)
{ {
Packet packet = Packet.FromBytes(steamPacket.Data); Packet packet = Packet.FromBytes(steamPacket.Data);
bool isTarget = packet.Info.receiverId == BORADCAST_ID || packet.Info.receiverId == BoneSync.lobby.GetLocalId(); bool isTarget = packet.Info.receiverId == BROADCAST_ID || packet.Info.receiverId == BoneSync.lobby.GetLocalId();
if (isTarget) if (isTarget)
{ {
Packet.OnPacketReceived(packet); Packet.OnPacketReceived(packet);
} }
} }
public override bool Tick() public int Tick()
{ {
if (!SteamClient.IsValid) return false; if (!SteamClient.IsValid) return 0;
int processed = 0; int processed = 0;
while (SteamNetworking.IsP2PPacketAvailable()) while (SteamNetworking.IsP2PPacketAvailable())
{ {
@@ -69,38 +75,65 @@ namespace BoneSync.Networking.Transport
ProcessPacket(packet.Value); ProcessPacket(packet.Value);
processed++; processed++;
} }
return processed > 0; return processed;
} }
public void SendP2P(SteamId peer, byte[] data) public P2PSend GetSteamSendType(PacketReliability packetReliability)
{ {
switch (packetReliability)
{
case PacketReliability.Unreliable:
return P2PSend.UnreliableNoDelay;
case PacketReliability.Reliable:
return P2PSend.Reliable;
default:
return P2PSend.Reliable;
}
}
public void SendP2P(SteamId peer, byte[] data, PacketReliability sendType)
{
if (peer == BoneSync.lobby.GetLocalId()) if (peer == BoneSync.lobby.GetLocalId())
{ {
//MelonLogger.Msg("Trying to send packet to self"); //SyncLogger.Msg("Trying to send packet to self");
return; return;
} }
SteamNetworking.SendP2PPacket(peer, data, data.Length, 0, P2PSend.Reliable);
if (sendType == PacketReliability.ReliableFast)
{
SteamNetworking.SendP2PPacket(peer, data, data.Length, 0, P2PSend.UnreliableNoDelay);
SteamNetworking.SendP2PPacket(peer, data, data.Length, 0, P2PSend.Reliable);
return;
}
P2PSend steamSendType = GetSteamSendType(sendType);
SteamNetworking.SendP2PPacket(peer, data, data.Length, 0, steamSendType);
} }
public override void Send(Packet packet) public void Send(Packet packet)
{ {
LobbyManager.LobbyManager _instance = BoneSync.lobby; LobbyManager.LobbyManager _instance = BoneSync.lobby;
if (_instance == null) if (_instance == null)
{ {
MelonLogger.Msg("Lobby instance is null"); SyncLogger.Msg("Lobby instance is null");
return; return;
} }
if (packet.Info.receiverId == BORADCAST_ID) if (packet.Info.receiverId == BROADCAST_ID)
{ {
foreach (SteamId peer in _instance.GetPeers()) foreach (SteamId peer in _instance.GetPeers())
{ {
SendP2P(peer, packet.ToBytes()); SendP2P(peer, packet.ToBytes(), packet.Info.reliability);
} }
return; return;
} }
else else
{ {
SendP2P(packet.Info.receiverId, packet.ToBytes()); SendP2P(packet.Info.receiverId, packet.ToBytes(), packet.Info.reliability);
} }
} }

View File

@@ -6,11 +6,11 @@ using System.Threading.Tasks;
namespace BoneSync.Networking.Transport namespace BoneSync.Networking.Transport
{ {
public abstract class TransportBase public interface ITransportBase
{ {
public const int BORADCAST_ID = 0; ulong BROADCAST_ID { get; }
public abstract void Send(Packet packet); void Send(Packet packet);
public abstract bool Tick(); int Tick();
public abstract void CleanUp(); void CleanUp();
} }
} }

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Sync.Components;
using HarmonyLib;
using PuppetMasta;
namespace BoneSync.Patching
{
[HarmonyPatch(typeof(SubBehaviourHealth))]
internal class AIHealthPatches
{
/*[HarmonyPatch(nameof(SubBehaviourHealth.TakeDamage)), HarmonyPrefix]
private static bool DamagePrefix(SubBehaviourHealth __instance)
{
if (!BoneSync.IsConnected) return true;
if (CallPatchedMethods.allowPatchedMethodCall) return true;
Syncable syncable = __instance.behaviour.GetComponentInParent<Syncable>();
if (syncable != null && syncable.Registered && !syncable.isOwner) return false;
return true;
}*/
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using HarmonyLib;
using MelonLoader;
using StressLevelZero.Environment;
using StressLevelZero.Interaction;
using UnityEngine;
using UnityEngine.Events;
namespace BoneSync.Patching
{
[HarmonyPatch(typeof(ButtonToggle))]
internal class ButtonTogglePatches
{
[HarmonyPatch(nameof(ButtonToggle.OnEnable)), HarmonyPostfix]
private static void OnEnablePostfix(ButtonToggle __instance)
{
Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance);
}
}
}

View File

@@ -0,0 +1,97 @@
using BoneSync.Data;
using StressLevelZero.Combat;
using StressLevelZero.Environment;
using StressLevelZero.Pool;
using StressLevelZero.Props;
using StressLevelZero.Props.Weapons;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;
using Cart = StressLevelZero.Environment.Cart;
namespace BoneSync.Patching
{
public static class CallPatchedMethods
{
public static bool allowPatchedMethodCall
{
private set;
get;
}
public static void TakeDamage(ObjectDestructable __instance, Vector3 normal, float damage, bool crit, AttackType attackType)
{
allowPatchedMethodCall = true;
__instance.TakeDamage(normal, damage, crit, attackType);
allowPatchedMethodCall = false;
}
public static void TakeDamage(Prop_Health __instance, float damage, bool crit, AttackType attackType)
{
allowPatchedMethodCall = true;
__instance.TAKEDAMAGE(damage, crit, attackType);
allowPatchedMethodCall = false;
}
public static void FireGun(Gun __instance)
{
allowPatchedMethodCall = true;
__instance.Fire();
allowPatchedMethodCall = false;
}
public static void CartGo(Cart __instance)
{
if (__instance == null) return;
allowPatchedMethodCall = true;
__instance.Go();
allowPatchedMethodCall = false;
}
public static void CartLaunch(Cart __instance)
{
if (__instance == null) return;
allowPatchedMethodCall = true;
__instance.Launch();
allowPatchedMethodCall = false;
}
public static void CartGoBackward(Cart __instance)
{
if (__instance == null) return;
allowPatchedMethodCall = true;
__instance.GoBackward();
allowPatchedMethodCall = false;
}
public static void CartGoForward(Cart __instance)
{
if (__instance == null) return;
allowPatchedMethodCall = true;
__instance.GoForward();
allowPatchedMethodCall = false;
}
public static void BypassPatchInvoke(this UnityEvent e)
{
SyncLogger.Msg("Bypassing patched UnityEvent invoke for event " + e.GetHashCode());
UnityEventPatching.GetOriginalEvent(e).Invoke(); // invoke the original event directly
}
public static Poolee InstantiatePoolee(Pool pool, Vector3 position, Quaternion rotation, Vector3 scale)
{
allowPatchedMethodCall = true;
Poolee poolee = pool.InstantiatePoolee(position, rotation);
poolee.transform.position = position;
poolee.transform.rotation = rotation;
poolee.transform.localScale = scale;
poolee.transform.parent = null;
poolee.gameObject.SetActive(true);
allowPatchedMethodCall = false;
return poolee;
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Networking.Messages;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using HarmonyLib;
using StressLevelZero.Environment;
namespace BoneSync.Patching
{
[HarmonyPatch(typeof(Cart))]
public class CartPatches
{
public static bool CartEvent(Cart cart, SimpleEventType eventType)
{
if (CallPatchedMethods.allowPatchedMethodCall) return true;
SyncLogger.Msg("CartEvent: " + cart.transform.GetPath() + " " + eventType);
Syncable syncable = ObjectSync.MakeOrGetSyncable(cart);
if (syncable == null)
{
SyncLogger.Error("CartEvent: syncable is null");
return false;
}
return syncable.AddSimpleEventToQueue(eventType);
}
[HarmonyPatch(nameof(Cart.Go)), HarmonyPrefix]
public static bool CartStartPostfix(Cart __instance)
{
return CartEvent(__instance, SimpleEventType.CartGo);
}
[HarmonyPatch(nameof(Cart.Launch)), HarmonyPrefix]
public static bool CartStopPostfix(Cart __instance)
{
return CartEvent(__instance, SimpleEventType.CartLaunch);
}
[HarmonyPatch(nameof(Cart.GoBackward)), HarmonyPrefix]
public static bool CartGoBackwardPostfix(Cart __instance)
{
return CartEvent(__instance, SimpleEventType.CartGoBackwards);
}
[HarmonyPatch(nameof(Cart.GoForward)), HarmonyPrefix]
public static bool CartGoForwardPostfix(Cart __instance)
{
return CartEvent(__instance, SimpleEventType.CartGoForward);
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HarmonyLib;
using StressLevelZero.AI;
using StressLevelZero.Combat;
using StressLevelZero.Pool;
using UnityEngine;
namespace BoneSync.Patching
{
/*[HarmonyPatch(typeof(PoolSpawner))]
internal class DebugPatches
{
// patch the static method "PoolSpawner.SpawnProjectile"
[HarmonyPatch(nameof(PoolSpawner.SpawnProjectile)), HarmonyPrefix]
private static void SpawnProjectilePrefix(Vector3 position, Quaternion rotation, BulletObject bulletObject, string weaponName, TriggerRefProxy proxy)
{
SyncLogger.Msg("PoolSpawner.SpawnProjectile " + weaponName);
}
}*/
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using HarmonyLib;
using MelonLoader;
using StressLevelZero.Interaction;
namespace BoneSync.Patching
{
// Credit: Entanglement
// this patch is based on the one from Entanglement
[HarmonyPatch(typeof(ForcePullGrip), nameof(ForcePullGrip.OnFarHandHoverUpdate))]
public class ForcePullPatch
{
public static void Prefix(ForcePullGrip __instance, ref bool __state, Hand hand)
{
__state = __instance.pullCoroutine != null;
}
public static void Postfix(ForcePullGrip __instance, ref bool __state, Hand hand)
{
if (!(__instance.pullCoroutine != null && !__state))
return;
SyncLogger.Msg("ForcePullGrip.OnFarHandHoverUpdate: " + __instance.name + " Hand: " + hand.name);
InteractableHost interactableHost = __instance?.grip?.host;
if (interactableHost == null)
{
SyncLogger.Error("InteractableHost is null for " + __instance.name);
return;
}
Syncable syncable = ObjectSync.MakeOrGetSyncable(interactableHost);
syncable?.RegisterSyncable();
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Networking.Messages;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using HarmonyLib;
using MelonLoader;
using StressLevelZero.Props.Weapons;
namespace BoneSync.Patching
{
[HarmonyPatch(typeof(Gun))]
public class GunPatches
{
[HarmonyPatch(nameof(Gun.Fire)), HarmonyPrefix]
public static bool FirePatch(Gun __instance)
{
return true;
}
[HarmonyPatch(nameof(Gun.OnFire)), HarmonyPrefix]
public static void OnFirePatch(Gun __instance)
{
SyncLogger.Msg("Gun.OnFire: " + __instance.name);
if (!BoneSync.IsConnected) return;
Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance.gameObject);
if (syncable == null) return;
if (!syncable.Registered) return;
if (syncable.isOwner)
{
SyncLogger.Msg("Gun.OnFire: " + __instance.name + " is owner");
GunSyncInfo gunSyncInfo = new GunSyncInfo()
{
cartridgeState = __instance.cartridgeState,
hammerState = __instance.hammerState,
syncId = syncable.GetSyncId(),
messageType = GunSyncMessageType.Fire,
bulletObject = __instance.chamberedCartridge
};
GunSyncMessage gunSyncMessage = new GunSyncMessage(gunSyncInfo);
gunSyncMessage.Broadcast();
SyncLogger.Msg("Gun.OnFire: " + __instance.name + " sent message");
}
return;
}
}
}

View File

@@ -0,0 +1,50 @@
using BoneSync.Data;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using HarmonyLib;
using MelonLoader;
using StressLevelZero.Interaction;
using StressLevelZero.Props.Weapons;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
namespace BoneSync.Patching
{
[HarmonyPatch(typeof(HandWeaponSlotReciever))]
internal class HolsterSlotPatches
{
[HarmonyPatch(nameof(HandWeaponSlotReciever.MakeStatic)), HarmonyPrefix]
public static void StaticPatch(HandWeaponSlotReciever __instance)
{
SyncLogger.Msg("HandWeaponSlotReciever.MakeStatic: " + __instance.name);
InteractableHost interactableHost = __instance.m_WeaponHost;
if (interactableHost == null)
{
SyncLogger.Error("InteractableHost is null for " + __instance.transform.GetPath());
return;
}
Syncable syncable = ObjectSync.MakeOrGetSyncable(interactableHost);
syncable?.SetInHolster(true);
}
[HarmonyPatch(nameof(HandWeaponSlotReciever.MakeDynamic)), HarmonyPrefix]
public static void DynamicPatch(HandWeaponSlotReciever __instance)
{
SyncLogger.Msg("HandWeaponSlotReciever.MakeDynamic: " + __instance.name);
InteractableHost interactableHost = __instance.m_WeaponHost;
if (interactableHost == null)
{
SyncLogger.Error("InteractableHost is null for " + __instance.transform.GetPath());
return;
}
Syncable syncable = ObjectSync.MakeOrGetSyncable(interactableHost);
syncable?.SetInHolster(false);
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Sync; using BoneSync.Sync;
using BoneSync.Sync.Components; using BoneSync.Sync.Components;
using HarmonyLib; using HarmonyLib;
@@ -18,24 +19,19 @@ namespace BoneSync.Patching
[HarmonyPatch(nameof(InteractableHost.OnEnable)), HarmonyPostfix] [HarmonyPatch(nameof(InteractableHost.OnEnable)), HarmonyPostfix]
public static void OnEnablePatch(InteractableHost __instance) public static void OnEnablePatch(InteractableHost __instance)
{ {
//MelonLoader.MelonLogger.Msg("InteractableHost enabled: " + __instance.name + " Manager: " + __instance?.manager?.name); //SyncLogger.Msg("InteractableHost enabled: " + __instance.name + " Manager: " + __instance?.manager?.name);
ObjectSync.MakeOrGetSyncable(__instance); Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance);
}
[HarmonyPatch(nameof(InteractableHost.OnDestroy)), HarmonyPostfix]
public static void OnDestroyPatch(InteractableHost __instance)
{
MelonLogger.Msg("InteractableHost destroyed: " + __instance.name);
} }
[HarmonyPatch(nameof(InteractableHost.AttachHand)), HarmonyPostfix] [HarmonyPatch(nameof(InteractableHost.AttachHand)), HarmonyPostfix]
public static void AttachHandPatch(InteractableHost __instance, Hand hand) public static void AttachHandPatch(InteractableHost __instance, Hand hand)
{ {
MelonLogger.Msg("InteractableHost attached to hand: " + __instance.name + " Hand: " + hand.name); SyncLogger.Msg("InteractableHost attached to hand: " + __instance.name + " Hand: " + hand.name);
Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance); Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance);
if (syncable == null) if (syncable == null)
{ {
MelonLogger.Error("Syncable is null for " + __instance.name); SyncLogger.Error("Syncable is null for " + __instance.name);
return; return;
} }
syncable.RegisterSyncable(); syncable.RegisterSyncable();
@@ -45,7 +41,7 @@ namespace BoneSync.Patching
/*[HarmonyPatch(nameof(InteractableHost.AddForcePullGrip)), HarmonyPostfix] /*[HarmonyPatch(nameof(InteractableHost.AddForcePullGrip)), HarmonyPostfix]
public static void AddForcePullGripPatch(InteractableHost __instance, ForcePullGrip grip) public static void AddForcePullGripPatch(InteractableHost __instance, ForcePullGrip grip)
{ {
MelonLoader.MelonLogger.Msg("AddForcePullGrip to hand: " + __instance.name + " Hand: " + grip.name); SyncLogger.Msg("AddForcePullGrip to hand: " + __instance.name + " Hand: " + grip.name);
}*/ }*/
} }
@@ -56,7 +52,7 @@ namespace BoneSync.Patching
[HarmonyPatch(nameof(InteractableHostManager.Start)), HarmonyPostfix] [HarmonyPatch(nameof(InteractableHostManager.Start)), HarmonyPostfix]
public static void OnEnablePatch(InteractableHostManager __instance) public static void OnEnablePatch(InteractableHostManager __instance)
{ {
MelonLoader.MelonLogger.Msg("InteractableHostManager started: " + __instance.transform.GetPath()); SyncLogger.Msg("InteractableHostManager started: " + __instance.transform.GetPath());
ObjectSync.MakeOrGetSyncable(__instance); ObjectSync.MakeOrGetSyncable(__instance);
} }
} }

View File

@@ -0,0 +1,87 @@
using BoneSync.Networking.Messages;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using HarmonyLib;
using StressLevelZero.Combat;
using StressLevelZero.Data;
using StressLevelZero.Props;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace BoneSync.Patching
{
[HarmonyPatch(typeof(ObjectDestructable))]
public class ObjectDestructablePatches
{
[HarmonyPatch(nameof(ObjectDestructable.TakeDamage))]
[HarmonyPrefix]
private static bool TakeDamagePatch(ObjectDestructable __instance, ref Vector3 normal, ref float damage, ref bool crit, ref AttackType attackType)
{
if (CallPatchedMethods.allowPatchedMethodCall) return true;
if (!BoneSync.IsConnected) return true;
if (damage < 0.05f) return true; // ignore small damage (e.g. a little bit of fall damage)
//SyncLogger.Msg("ObjectDestructable.TakeDamage: " + damage);
Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance.gameObject);
if (syncable != null)
{
if (damage > 0.5f) syncable.RegisterSyncable(); // register syncable if damage is very significant, e.g. a bullet hit
if (syncable.Registered && !syncable.isOwner) return false;
//SyncLogger.Msg("Patch: ObjectDestructable.TakeDamage: " + damage + " " + syncable.gameObject.name);
ObjectHealthInfo objectHealthInfo = new ObjectHealthInfo(__instance._health, __instance._hits, normal, damage, crit, attackType, 0);
ObjectSync.SendObjectDamageMessage(syncable, ObjectDamageType.DestructibleTakeDamage, objectHealthInfo);
return false; // prevent networked objects from taking damage locally
}
return true;
}
/*// patch the getter for lootTable to return null if the object is networked
[HarmonyPatch(nameof(ObjectDestructable.lootTable), MethodType.Getter)]
[HarmonyPrefix]
private static bool LootTablePatch(ObjectDestructable __instance, ref LootTableData __result)
{
if (!BoneSync.IsConnected) return true;
Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance.gameObject);
if (syncable != null && !BoneSync.lobby.IsHost)
{
__result = null;
return false;
}
return true;
}*/
}
[HarmonyPatch(typeof(Prop_Health))]
public class PropHealthPatches
{
[HarmonyPatch(nameof(Prop_Health.TAKEDAMAGE))]
[HarmonyPrefix]
private static bool TakeDamagePatch(Prop_Health __instance, ref float damage, ref bool crit, ref AttackType attackType)
{
if (CallPatchedMethods.allowPatchedMethodCall) return true;
if (!BoneSync.IsConnected) return true;
//SyncLogger.Msg("Prop_Health.TAKEDAMAGE: " + damage);
Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance.gameObject);
if (syncable != null)
{
if (damage > 0.5f) syncable.RegisterSyncable();
if (syncable.Registered && !syncable.isOwner) return false;
//SyncLogger.Msg("Patch: Prop_Health.TAKEDAMAGE: " + damage + " " + syncable.gameObject.name);
ObjectHealthInfo objectHealthInfo = new ObjectHealthInfo(__instance.cur_Health, __instance.hits, Vector3.zero, damage, crit, attackType, 0);
ObjectSync.SendObjectDamageMessage(syncable, ObjectDamageType.PropHealthTakeDamage, objectHealthInfo);
return false; // prevent networked objects from taking damage locally
}
return true;
}
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Networking.Messages;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using HarmonyLib;
using MelonLoader;
using StressLevelZero.Interaction;
namespace BoneSync.Patching
{
public static class AlignPlugPatches
{
public static void OnPlugSocketChange(AlignPlug plug, Socket socket)
{
Syncable plugSyncable = plug.GetComponentInParent<Syncable>();
Syncable socketSyncable = socket?.GetComponentInParent<Syncable>();
if (!plugSyncable)
{
SyncLogger.Warning("PlugSyncable not found");
return;
}
plugSyncable.FindAndUpdateComponents();
if (!plugSyncable.Registered)
{
SyncLogger.Warning("PlugSyncable not registered");
return;
}
if (!socketSyncable)
{
SyncLogger.Warning("SocketSyncable not found");
} else
{
socketSyncable.FindAndUpdateComponents();
}
if (socketSyncable != null && socketSyncable.isOwner && !plugSyncable.isOwner)
{
plugSyncable.TryBecomeOwner(true); // forcefully take ownership of the plug
}
byte plugId = plugSyncable.GetPlugId(plug);
byte socketId = socketSyncable != null ? socketSyncable.GetSocketId(socket) : byte.MaxValue;
//SyncLogger.Msg("AlignPlug state:" + plug.transform.GetPath() + " Socket:" + socket?.transform?.GetPath() + " PlugID:" + plugId + " SocketID:" + socketId);
if (!plugSyncable.isOwner) return;
PlugSyncMessageData messageData = new PlugSyncMessageData
{
plugSyncId = plugSyncable.GetSyncId(),
plugIndex = plugId,
socketSyncId = socketSyncable != null ? socketSyncable.GetSyncId() : ushort.MinValue,
socketIndex = socketId
};
PlugSyncMessage plugSyncMessage = new PlugSyncMessage(messageData);
plugSyncMessage.Broadcast();
}
// for some reason AlignPlug.OnPlugInsertComplete can't be patched directly
/*
[HarmonyPatch(nameof(AlignPlug.OnPlugExitComplete)), HarmonyPostfix]
public static void AlignPlugEjectPatch(AlignPlug __instance)
{
SyncLogger.Msg("AlignPlug ejected: " + __instance.transform.GetPath());
OnPlugSocketChange(__instance, null);
}*/
}
[HarmonyPatch(typeof(MagazinePlug))]
public static class MagazinePlugPatches
{
[HarmonyPatch(nameof(MagazinePlug.OnPlugInsertComplete)), HarmonyPostfix]
public static void MagazinePlugInsertPatch(MagazinePlug __instance)
{
SyncLogger.Msg("MagazinePlug inserted: " + __instance.transform.GetPath());
AlignPlugPatches.OnPlugSocketChange(__instance, __instance.GetSocket());
}
[HarmonyPatch(nameof(MagazinePlug.OnPlugExitComplete)), HarmonyPostfix]
public static void MagazinePlugEjectPatch(MagazinePlug __instance)
{
SyncLogger.Msg("MagazinePlug ejected: " + __instance.transform.GetPath());
AlignPlugPatches.OnPlugSocketChange(__instance, null);
}
}
}

View File

@@ -1,41 +1,211 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Networking.Messages;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using HarmonyLib; using HarmonyLib;
using MelonLoader; using MelonLoader;
using StressLevelZero; using StressLevelZero;
using StressLevelZero.Pool; using StressLevelZero.Pool;
using StressLevelZero.Props.Weapons;
using UnityEngine; using UnityEngine;
namespace BoneSync.Patching namespace BoneSync.Patching
{ {
/* public static class PoolBlacklist
[HarmonyPatch(typeof(Pool))]
internal class PoolPatches
{ {
private static Dictionary<Pool, bool> _blacklistedCache = new Dictionary<Pool, bool>();
private static Dictionary<Pool, bool> _clientSpawnCache = new Dictionary<Pool, bool>();
[HarmonyPatch(nameof(Pool.Spawn))] private static bool _isBlacklistedPool(Pool pool)
[HarmonyPostfix]
private static void SpawnPatch(Pool __instance, ref GameObject __result)
{ {
MelonLogger.Msg("Spawned object: " + __result.name); if (pool.name.Contains("RigidbodyProjectile")) return true;
if (pool.name.Contains("VFX Despawn Mesh")) return true;
if (pool.name.Contains("AudioPlayer")) return true;
if (pool.name.Contains("Decal")) return true;
if (pool.name.Contains("PopupText")) return true;
//if (pool.name.Contains("Nimbus")) return true;
if (pool.Prefab.GetComponent<SpawnFragment>() != null) return true;
if (pool.Prefab.GetComponent<SpawnFragmentArray>() != null) return true;
return false;
} }
[HarmonyPatch(nameof(Pool.FlagPooleeForRespawn))] private static bool _isClientSpawnPool(Pool pool)
[HarmonyPostfix]
private static void FlagPooleeForRespawnPatch(Pool __instance, ref GameObject p)
{ {
MelonLogger.Msg("Flagged object for respawn: " + p.name); if (pool.Prefab.GetComponent<Magazine>() != null) return true;
return false;
} }
[HarmonyPatch(nameof(Pool.InstantiatePoolee))] public static bool IsBlacklistedPool(Pool pool)
[HarmonyPostfix]
private static void InstantiatePooleePatch(Pool __instance, ref Poolee __result)
{ {
MelonLogger.Msg("Instantiated object: " + __result.name); if (_blacklistedCache.ContainsKey(pool))
return _blacklistedCache[pool];
_blacklistedCache[pool] = _isBlacklistedPool(pool);
return _blacklistedCache[pool];
}
public static bool IsClientSpawnPool(Pool pool)
{
if (_clientSpawnCache.ContainsKey(pool))
return _clientSpawnCache[pool];
_clientSpawnCache[pool] = _isClientSpawnPool(pool);
return _clientSpawnCache[pool];
}
}
[HarmonyPatch(typeof(Pool))]
public static class PoolPatches
{
[HarmonyPatch(nameof(Pool.InstantiatePoolee), new Type[] { typeof(Vector3), typeof(Quaternion) }), HarmonyPrefix]
private static bool InstantiatePooleePatchPre(Pool __instance)
{
if (!__instance.Prefab)
return false;
return true;
}
[HarmonyPatch(nameof(Pool.InstantiatePoolee), new Type[] { typeof(Vector3), typeof(Quaternion) }), HarmonyPostfix]
private static void InstantiatePooleePatchPost(Pool __instance, Poolee __result, Vector3 position, Quaternion rotation)
{
if (CallPatchedMethods.allowPatchedMethodCall) return;
if (__instance == null) return;
if (PoolBlacklist.IsBlacklistedPool(__instance)) return;
//SyncLogger.Msg("Patched Instantiating object in pool: " + __instance.name);
__result.onSpawnDelegate = Il2CppSystem.Delegate.Combine(__result.onSpawnDelegate, (Il2CppSystem.Action<GameObject>)((g) => { PooleePatches.OnSpawnPatchPost(__result); })).Cast<Il2CppSystem.Action<GameObject>>();
__result.onDespawnDelegate = Il2CppSystem.Delegate.Combine(__result.onDespawnDelegate, (Il2CppSystem.Action<GameObject>)((g) => { PooleePatches.OnDespawnPatchPost(__result); })).Cast<Il2CppSystem.Action<GameObject>>();
} }
}*/ /*[HarmonyPatch(nameof(Pool.Spawn))]
[HarmonyPostfix]
private static void SpawnPatchPost(Pool __instance, ref GameObject __result)
{
if (__instance == null) return;
if (__instance.Prefab == null) return;
if (CallPatchedMethods.allowPatchedMethodCall) return;
if (PoolBlacklist.isBlacklistedPool(__instance)) return;
if (BoneSync.IsConnected)
{
SyncLogger.Msg("Patched Spawning object in pool: " + __instance.name);
bool isHost = BoneSync.lobby.IsHost;
if (!isHost) GameObject.DestroyImmediate(__result);
}
}*/
/*[HarmonyPatch(nameof(Pool.Spawn))]
[HarmonyPrefix]
private static bool SpawnPatchPre(Pool __instance, ref Vector3 position, ref Quaternion rotation, ref Il2CppSystem.Nullable<Vector3> scale, ref Il2CppSystem.Nullable<bool> autoEnable)
{
if (__instance == null) return true;
if (__instance.Prefab == null) return true;
if (CallPatchedMethods.allowPatchedMethodCall) return true;
if (PoolBlacklist.isBlacklistedPool(__instance)) return true;
if (BoneSync.IsConnected)
{
SyncLogger.Msg("Patched Spawning object in pool: " + __instance.name);
return BoneSync.lobby.IsHost; // only allow host to spawn objects naturally
}
__instance.
return true;
}
*/
}
[HarmonyPatch(typeof(Poolee))]
public static class PooleePatches
{
public static void OnSpawnPatchPost(Poolee __instance)
{
if (CallPatchedMethods.allowPatchedMethodCall) return;
if (!BoneSync.IsConnected) return;
SyncLogger.Msg("Poolee.OnSpawn: " + __instance.gameObject.transform.GetPath());
Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance);
bool spawnNormally = BoneSync.lobby.IsHost || PoolBlacklist.IsClientSpawnPool(__instance.pool);
SyncLogger.Msg("Poolee.OnSpawn: " + __instance.gameObject.transform.GetPath() + " syncid:" + syncable?.GetSyncId() + " spawnNormally" + spawnNormally);
if (!spawnNormally) {
MelonCoroutines.Start(OnSpawnClient(__instance)); // block object from spawning
return;
}
if (syncable == null) return;
//
//
//
//.Msg("Poolee.OnSpawn: " + __instance.gameObject.transform.GetPath() + " " + syncable.GetSyncId());
}
public static void OnDespawnPatchPost(Poolee __instance)
{
if (CallPatchedMethods.allowPatchedMethodCall) return;
if (!BoneSync.IsConnected) return;
SyncLogger.Msg("Poolee.OnDespawn: " + __instance.gameObject.transform.GetPath());
//Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance);
//if (syncable == null) return;
//if (syncable.isOwner) return;
//syncable.DiscardSyncable(true);
}
public static IEnumerator OnSpawnClient(Poolee poolee)
{
GameObject go = poolee.gameObject;
if (SceneLoader.loading)
{
while (SceneLoader.loading)
{
go.SetActive(false);
yield return null;
}
}
for (int i = 0; i < 2; i++)
{
go.SetActive(false);
yield return null;
}
}
public static IEnumerator OnSpawnHost(Poolee poolee)
{
return null;
}
/* // DONT USE THIS PATCH, CRASHES GAME WITH NO ERROR
[HarmonyPatch(nameof(Poolee.Despawn), new Type[] { typeof(Il2CppSystem.Nullable<bool>), typeof(Il2CppSystem.Nullable<Color>) }), HarmonyPrefix]
private static bool DespawnPatchPre(Poolee __instance, ref Il2CppSystem.Nullable<bool> playVFX, ref Il2CppSystem.Nullable<Color> despawnColor)
{
if (CallPatchedMethods.allowPatchedMethodCall) return true;
if (!BoneSync.IsConnected) return true;
Syncable syncable = ObjectSync.MakeOrGetSyncable(__instance);
if (syncable == null) return true;
if (!syncable.isOwner) return false; // if not owner, don't despawn
return true;
}
*/
}
} }

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Sync;
using BoneSync.Sync.Components;
using HarmonyLib;
using MelonLoader;
using StressLevelZero.Data;
using StressLevelZero.Pool;
using UnityEngine;
using static StressLevelZero.VFX.ParticleSpreadManager;
namespace BoneSync.Patching
{
[HarmonyPatch(typeof(PrefabSpawner))]
internal class PrefabSpawnerPatches
{
[HarmonyPatch(nameof(PrefabSpawner.SpawnPrefab), new Type[] { typeof(Transform), typeof(Vector3), typeof(Quaternion) }), HarmonyPrefix]
private static bool SpawnPrefabPrefix(PrefabSpawner __instance, Transform AttachTo, Vector3 Position, Quaternion Rotation)
{
if (!BoneSync.IsConnected) return true;
if (!BoneSync.lobby.IsHost) return false;
Syncable syncable = SpawnPrefabSyncable(__instance, AttachTo, Position, Rotation);
return false;
}
[HarmonyPatch(nameof(PrefabSpawner.SpawnPrefab), new Type[] { }), HarmonyPrefix]
private static bool SpawnPrefabSimplePrefix(PrefabSpawner __instance)
{
return SpawnPrefabPrefix(__instance, null, __instance.transform.position, __instance.transform.rotation);
}
public static Syncable SpawnPrefabSyncable(PrefabSpawner __instance, Transform AttachTo, Vector3 Position, Quaternion Rotation)
{
SyncLogger.Msg("Spawning prefab: " + __instance.Prefab.name);
SpawnableObject spawnable = SpawnableManager.GetSpawnableByPrefabName(__instance.Prefab.name);
if (spawnable == null)
{
SyncLogger.Error("Spawnable not found for prefab: " + __instance.Prefab.name);
return null;
}
Poolee poolee = SpawnableManager.SpawnPoolee(spawnable, Position, Rotation);
Syncable syncable = ObjectSync.MakeOrGetSyncable(poolee);
syncable.RegisterSyncable();
return syncable;
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Sync;
using HarmonyLib;
using MelonLoader;
using StressLevelZero.Utilities;
namespace BoneSync.Patching
{
[HarmonyPatch(typeof(BoneworksSceneManager))]
internal class SceneManagerPatches
{
/*[HarmonyPatch(nameof(BoneworksSceneManager.LoadScene), new Type[] { typeof(int) }), HarmonyPrefix]
private static void LoadSceneIndexPrefix(int sceneBuildIndex)
{
SyncLogger.Msg("LoadScenePrefix: " + sceneBuildIndex);
if (!BoneSync.IsConnected) return;
if (BoneSync.lobby.IsHost)
{
SyncLogger.Msg("Host is loading scene, sending message to clients...");
SceneSync.SendSceneSyncMessage(sceneBuildIndex);
}
}
[HarmonyPatch(nameof(BoneworksSceneManager.LoadScene), new Type[] { typeof(string) }), HarmonyPostfix]
private static void LoadSceneStringPrefix(string sceneName)
{
SyncLogger.Msg("LoadScenePrefix: " + sceneName);
if (!BoneSync.IsConnected) return;
if (BoneSync.lobby.IsHost)
{
//SyncLogger.Msg("Host is loading scene, sending message to clients...");
//SceneSync.SendSceneSyncMessage(sceneName);
}
}*/
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using HarmonyLib;
using MelonLoader;
using StressLevelZero.Rig;
using BoneSync.Data;
using StressLevelZero;
namespace BoneSync.Patching
{
[HarmonyPatch(typeof(SkeletonHand))]
internal static class SkeletonHandPatches
{
public static byte poseIndexRight = 0;
public static float radiusRight = 0.0f;
public static byte poseIndexLeft = 0;
public static float radiusLeft = 0.0f;
private static Dictionary<string, byte> handPoses = new Dictionary<string, byte>();
private static byte GetHandPoseIndex(string handPoseName)
{
if (handPoses.ContainsKey(handPoseName))
return handPoses[handPoseName];
if (PlayerScripts.playerHandPoses == null || PlayerScripts.playerHandPoses.Count == 0)
{
SyncLogger.Msg("PlayerScripts.playerHandPoses is empty, getting hand poses...");
PlayerScripts.GetHandPoses();
}
bool found = PlayerScripts.playerHandPoses.Contains(handPoseName);
if (!found)
{
//SyncLogger.Error($"Hand pose {handPoseName} not found in playerHandPoses!");
return 0;
}
byte index = (byte)PlayerScripts.playerHandPoses.IndexOf(handPoseName);
handPoses.Add(handPoseName, index);
return index;
}
[HarmonyPatch(nameof(SkeletonHand.SetHandPose)), HarmonyPrefix]
private static void SetHandPosePostfix(SkeletonHand __instance, string handPoseName)
{
if (!__instance.GetCharacterAnimationManager()) return;
SyncLogger.Msg($"SetHandPosePostfix: {handPoseName}");
int poseIndex = GetHandPoseIndex(handPoseName);
switch (__instance.handedness)
{
case Handedness.LEFT:
poseIndexLeft = (byte)poseIndex;
break;
case Handedness.RIGHT:
poseIndexRight = (byte)poseIndex;
break;
}
}
[HarmonyPatch(nameof(SkeletonHand.SetCylinderRadius)), HarmonyPrefix]
private static void SetCylinderRadiusPrefix(SkeletonHand __instance, float radius)
{
if (!__instance.GetCharacterAnimationManager()) return;
//SyncLogger.Msg($"SetCylinderRadiusPrefix: {radius}");
switch (__instance.handedness)
{
case Handedness.LEFT:
if (radiusLeft == radius) return;
SyncLogger.Msg($"SetCylinderRadiusPrefixLeft: {radius}");
radiusLeft = radius;
break;
case Handedness.RIGHT:
if (radiusRight == radius) return;
SyncLogger.Msg($"SetCylinderRadiusPrefixRight: {radius}");
radiusRight = radius;
break;
}
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Data;
using HarmonyLib;
using StressLevelZero.Interaction;
using UnityEngine;
using UnityEngine.Events;
namespace BoneSync.Patching
{
public class UnityEventPatching
{
internal static Dictionary<int, UnityEvent> _patchToOriginalMap = new Dictionary<int, UnityEvent>();
public static UnityEvent GetOriginalEvent(UnityEvent unityEvent)
{
int eventHash = unityEvent.GetHashCode();
if (_patchToOriginalMap.TryGetValue(eventHash, out UnityEvent originalEvent))
{
return originalEvent;
}
return unityEvent;
}
}
public class UnityEventPatch<T>
{
private static Dictionary<int, UnityEventPatch<T>> patches = new Dictionary<int, UnityEventPatch<T>>();
private T arg;
private UnityEvent unityEvent;
private Func<T, bool> handler;
private UnityEvent proxyUnityEvent;
//private float lastInvokeTime = 0f;
//private float invokeCooldown = 0.05f;
private UnityEventPatch(T arg, UnityEvent unityEvent, Func<T, bool> handler)
{
this.arg = arg;
this.unityEvent = unityEvent;
this.handler = handler;
this.proxyUnityEvent = new UnityEvent();
this.proxyUnityEvent.AddListener((UnityAction)OnEventInvoked);
int patchEventHash = proxyUnityEvent.GetHashCode();
patches[patchEventHash] = this; // avoid creating multiple patches for same event
UnityEventPatching._patchToOriginalMap[patchEventHash] = unityEvent;
}
public UnityEvent GetOriginalEvent()
{
return unityEvent;
}
private void OnEventInvoked()
{
bool allowInvoke = handler.Invoke(arg);
SyncLogger.Msg("UnityEventPatch invoked for event " + unityEvent.GetHashCode() + ", allowInvoke: " + allowInvoke);
if (allowInvoke)
{
unityEvent.Invoke(); // only invoke the original event if allowed
}
}
public UnityEvent GetPatchedEvent()
{
return proxyUnityEvent;
}
public static UnityEventPatch<T> Patch(T arg, UnityEvent unityEvent, Func<T, bool> handler)
{
int eventHash = unityEvent.GetHashCode();
UnityEventPatch<T> existingPatch = patches.TryGetValue(eventHash, out UnityEventPatch<T> foundPatch) ? foundPatch : null;
if (existingPatch != null)
{
return existingPatch;
}
UnityEventPatch<T> patch = new UnityEventPatch<T>(arg, unityEvent, handler);
patches[eventHash] = patch;
return patch;
}
}
}

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Data;
using BoneSync.Player;
using BoneSync.Sync.Components;
using HarmonyLib;
using StressLevelZero.Zones;
using UnityEngine;
namespace BoneSync.Patching
{
[HarmonyPatch(typeof(ZoneSpawner))]
public static class ZoneSpawnerPatch
{
// this patch should catch most of the spawning, but the pool spawning patch should catch the rest
[HarmonyPatch(nameof(ZoneSpawner.Spawn)), HarmonyPrefix]
public static bool ZoneSpawnPrefix(ZoneSpawner __instance)
{
if (!BoneSync.IsConnected) return true; // do not block if not connected
//.Msg("ZoneSpawner.Spawn: " + __instance.transform.GetPath());
if (BoneSync.lobby.IsHost)
{
return true;
}
return false; // don't spawn if not host
}
}
[HarmonyPatch(typeof(ZoneEncounter))]
public static class ZoneEncounterPatch
{
[HarmonyPatch(nameof(ZoneEncounter.StartEncounter)), HarmonyPrefix]
public static bool ZoneEncounterSpawnPrefix(ZoneEncounter __instance)
{
if (!BoneSync.IsConnected) return true;
SyncLogger.Msg("ZoneEncounter.StartEncounter: " + __instance.transform.GetPath());
if (BoneSync.lobby.IsHost)
{
return true;
}
return false;
}
}
// SceneZone and PlayerTrigger patches are based on the ones from entanglement mod
[HarmonyPatch(typeof(SceneZone))]
public static class SceneZonePatch
{
public static bool IsChildOfLocalRig(Transform transform)
{
if (PlayerRig.localRigWorldRoot == null) return false;
return transform.root == PlayerRig.localRigWorldRoot;
}
public static bool IsChildOfHostRig(Transform transform)
{
if (!BoneSync.IsConnected|| BoneSync.lobby.IsHost)
{
return IsChildOfLocalRig(transform);
}
else
{
PlayerRig hostRig = PlayerRig.GetPlayerRig(BoneSync.lobby.GetHostId());
Transform hostRoot = hostRig?.transform.root;
return hostRoot != null && transform.root == hostRoot;
}
}
[HarmonyPatch(nameof(SceneZone.OnTriggerEnter)), HarmonyPrefix]
public static bool EnterPrefix(SceneZone __instance, Collider other)
{
if (other.CompareTag("Player"))
{
return IsChildOfLocalRig(other.transform);
}
return true;
}
[HarmonyPatch(nameof(SceneZone.OnTriggerExit)), HarmonyPrefix]
public static bool ExitPrefix(SceneZone __instance, Collider other)
{
if (other.CompareTag("Player"))
{
return IsChildOfLocalRig(other.transform);
}
return true;
}
}
[HarmonyPatch(typeof(PlayerTrigger))]
public static class PlayerTriggerPatch
{
[HarmonyPatch(nameof(PlayerTrigger.OnTriggerEnter)), HarmonyPrefix]
public static bool EnterPrefix(PlayerTrigger __instance, Collider other)
{
if (other.CompareTag("Player"))
{
return SceneZonePatch.IsChildOfLocalRig(other.transform);
}
return true;
}
// only patch exit, as we want to allow activating a trigger always
[HarmonyPatch(nameof(PlayerTrigger.OnTriggerExit)), HarmonyPrefix]
public static bool ExitPrefix(PlayerTrigger __instance, Collider other)
{
if (other.CompareTag("Player"))
{
return SceneZonePatch.IsChildOfLocalRig(other.transform);
}
return true;
}
}
}

View File

@@ -11,38 +11,306 @@ using StressLevelZero.Player;
using StressLevelZero.VRMK; using StressLevelZero.VRMK;
using BoneSync.Networking; using BoneSync.Networking;
using BoneSync.Networking.Messages; using BoneSync.Networking.Messages;
using System.Reflection;
using System.IO;
using BoneSync.Data;
using StressLevelZero.Interaction;
using UnhollowerBaseLib;
using BoneSync.Patching;
using BoneSync.Sync.Components;
using Oculus.Platform.Models;
namespace BoneSync.Player namespace BoneSync.Player
{ {
internal class PlayerRig internal class PlayerRig
{ {
//public const string RIGMANAGER_SCENE_NAME = "[RigManager (Default Brett)]"; private const float RIG_SYNC_FPS = Syncable.OBJECT_SYNC_FPS;
private static List<PlayerRig> _playerRigs = new List<PlayerRig>(); public static AssetBundle rigBundle;
private static float _lastLocalSyncTime = 0;
private static Dictionary<ulong, PlayerRig> _playerRigs = new Dictionary<ulong, PlayerRig>();
private ulong _ownerId;
private GameObject playerRig; private GameObject playerRig;
private RigManager rigManager;
private SLZ_Body body; private SLZ_Body body;
private CharacterAnimationManager characterAnimationManager; private CharacterAnimationManager characterAnimationManager;
private Animator repAnimator; private Animator animator;
private Transform headTransform;
private Transform leftHandTransform;
private Transform rightHandTransform;
public Transform transform => playerRig.transform;
public static void LoadBundle()
{
rigBundle = EmebeddedAssetBundle.LoadFromAssembly("BoneSync.playerrep.eres");
if (rigBundle == null)
throw new NullReferenceException("playerRepBundle is null! Did you forget to compile the player bundle into the dll?");
SyncLogger.Msg("Loaded playerRepBundle success");
}
public static void Tick(float deltaTime)
{
foreach (PlayerRig playerRig in _playerRigs.Values)
{
try {
playerRig.UpdatePlayerTransforms(deltaTime);
playerRig.UpdateIK(deltaTime);
} catch (Exception e)
{
SyncLogger.Error("Error updating player rig for " + playerRig._ownerId);
}
}
}
public static void LocalSyncTick()
{
if (Time.realtimeSinceStartup - _lastLocalSyncTime > 1 / RIG_SYNC_FPS)
{
SendLocalPlayerSync();
_lastLocalSyncTime = Time.realtimeSinceStartup;
}
}
private void SetFingerCurl(Handedness handedness, SimpleFingerCurl fingerCurl)
{
characterAnimationManager.ApplyFingerCurl(handedness, 1f - fingerCurl.thumb, 1f - fingerCurl.index, 1f - fingerCurl.middle, 1f - fingerCurl.ring, 1f - fingerCurl.pinky);
}
private Vector3 _targetRootPos = Vector3.zero;
private Vector3 _targetHeadPos = Vector3.zero;
private Quaternion _targetHeadRot = Quaternion.identity;
private Vector3 _targetLeftHandPos = Vector3.zero;
private Quaternion _targetLeftHandRot = Quaternion.identity;
private Vector3 _targetRightHandPos = Vector3.zero;
private Quaternion _targetRightHandRot = Quaternion.identity;
private void UpdatePlayerTransforms(float deltaTime)
{
float step = deltaTime * 5f; // smoothing factor
playerRig.transform.position = Vector3.Lerp(playerRig.transform.position, _targetRootPos, step);
headTransform.position = Vector3.Lerp(headTransform.position, _targetHeadPos, step);
leftHandTransform.position = Vector3.Lerp(leftHandTransform.position, _targetLeftHandPos, step);
rightHandTransform.position = Vector3.Lerp(rightHandTransform.position, _targetRightHandPos, step);
headTransform.rotation = Quaternion.Lerp(headTransform.rotation, _targetHeadRot, step);
leftHandTransform.rotation = Quaternion.Lerp(leftHandTransform.rotation, _targetLeftHandRot, step);
rightHandTransform.rotation = Quaternion.Lerp(rightHandTransform.rotation, _targetRightHandRot, step);
}
public void UpdatePlayerSync(PlayerSyncInfo playerSyncInfo) public void UpdatePlayerSync(PlayerSyncInfo playerSyncInfo)
{ {
//playerRig.transform.ApplySimpleTransform(playerSyncInfo.headPos); EnsurePlayerRig();
//SyncLogger.Msg("Updating player sync for " + _ownerId);
//playerRig.transform.position = playerSyncInfo.rootPos;
_targetRootPos = playerSyncInfo.rootPos;
_targetHeadRot = playerSyncInfo.headPos.rotation;
_targetHeadPos = playerSyncInfo.headPos.position;
_targetLeftHandPos = playerSyncInfo.leftHandPos.position;
_targetLeftHandRot = playerSyncInfo.leftHandPos.rotation;
_targetRightHandPos = playerSyncInfo.rightHandPos.position;
_targetRightHandRot = playerSyncInfo.rightHandPos.rotation;
//headTransform.ApplySimpleTransform(playerSyncInfo.headPos);
//leftHandTransform.ApplySimpleTransform(playerSyncInfo.leftHandPos);
//rightHandTransform.ApplySimpleTransform(playerSyncInfo.rightHandPos);
SetFingerCurl(Handedness.LEFT, playerSyncInfo.leftHandFingerCurl);
SetFingerCurl(Handedness.RIGHT, playerSyncInfo.rightHandFingerCurl);
UpdatePose(Handedness.LEFT, playerSyncInfo.poseIndexLeft);
UpdatePose(Handedness.RIGHT, playerSyncInfo.poseIndexRight);
UpdatePoseRadius(Handedness.LEFT, playerSyncInfo.poseRadiusLeft);
UpdatePoseRadius(Handedness.RIGHT, playerSyncInfo.poseRadiusRight);
UpdatePlayerTransforms( 1f / RIG_SYNC_FPS); // immediately update transforms to avoid lag
} }
public PlayerRig() public void UpdatePose(Handedness hand, int index)
{ {
_playerRigs.Add(this); Il2CppStringArray handPoses = PlayerScripts.playerHandPoses;
if (handPoses == null)
{
SyncLogger.Msg("PlayerScripts.playerHandPoses is null, getting hand poses...");
PlayerScripts.GetHandPoses();
handPoses = PlayerScripts.playerHandPoses;
}
if (handPoses.Count < index + 1)
return;
UpdatePose(hand, handPoses[index]);
} }
public void UpdatePose(Handedness hand, string pose) => characterAnimationManager?.SetHandPose(hand, pose);
public void UpdatePoseRadius(Handedness hand, float radius) => characterAnimationManager?.SetCylinderRadius(hand, radius);
public static GameObject localPlayerRig;
public static Transform localRigSkeletonRoot;
public static Transform localRigWorldRoot;
private static Transform localRigHeadTransform;
private static Transform localRigLeftHandTransform;
private static Transform localRigRightHandTransform;
public static void SetLocalRigReferences()
{
if (localPlayerRig != null) return;
localPlayerRig = GameObject.Find("[RigManager (Default Brett)]/[SkeletonRig (GameWorld Brett)]");
localRigSkeletonRoot = localPlayerRig.transform;
localRigWorldRoot = localRigSkeletonRoot.root;
localRigHeadTransform = localRigSkeletonRoot.Find("Head");
localRigLeftHandTransform = localRigSkeletonRoot.Find("Hand (left)");
localRigRightHandTransform = localRigSkeletonRoot.Find("Hand (right)");
}
public static PlayerSyncInfo? GetLocalSyncInfo()
{
SetLocalRigReferences();
if (localPlayerRig == null)
{
SyncLogger.Msg("Local player rig not found");
return null;
}
if (localRigHeadTransform == null || localRigLeftHandTransform == null || localRigRightHandTransform == null)
{
SyncLogger.Msg("Local player rig components not found");
return null;
}
PlayerSyncInfo playerSyncInfo = new PlayerSyncInfo()
{
rootPos = localRigSkeletonRoot.position,
headPos = new SimpleSyncTransform(localRigHeadTransform, false),
leftHandPos = new SimpleSyncTransform(localRigLeftHandTransform, false),
rightHandPos = new SimpleSyncTransform(localRigRightHandTransform, false),
//leftHandFingerCurl = new SimpleFingerCurl(PlayerScripts.playerLeftHand.fingerCurl),
//rightHandFingerCurl = new SimpleFingerCurl(PlayerScripts.playerRightHand.fingerCurl)
poseIndexLeft = SkeletonHandPatches.poseIndexLeft,
poseIndexRight = SkeletonHandPatches.poseIndexRight,
poseRadiusLeft = SkeletonHandPatches.radiusLeft,
poseRadiusRight = SkeletonHandPatches.radiusRight
};
if (PlayerScripts.playerLeftHand)
playerSyncInfo.leftHandFingerCurl = new SimpleFingerCurl(PlayerScripts.playerLeftHand.fingerCurl);
if (PlayerScripts.playerRightHand)
playerSyncInfo.rightHandFingerCurl = new SimpleFingerCurl(PlayerScripts.playerRightHand.fingerCurl);
return playerSyncInfo;
}
private static void SendLocalPlayerSync()
{
if (!BoneSync.IsConnected) return;
//SyncLogger.Msg("Sending local player sync");
PlayerSyncInfo? playerSyncInfo = GetLocalSyncInfo();
if (!playerSyncInfo.HasValue) return;
PlayerSyncMessage playerSyncMessage = new PlayerSyncMessage(playerSyncInfo.Value);
playerSyncMessage.Broadcast();
}
public static PlayerRig GetPlayerRig(ulong ownerId)
{
if (_playerRigs.ContainsKey(ownerId))
{
//SyncLogger.Msg("PlayerRig already exists for " + ownerId);
return _playerRigs[ownerId];
}
if (rigBundle == null)
{
SyncLogger.Msg("playerRepBundle is null! Did you forget to load the bundle?");
return null;
}
//PlayerScripts.GetPlayerScripts();
PlayerRig rig = new PlayerRig(ownerId);
return rig;
}
public static void DestroyRig(ulong ownerId)
{
if (_playerRigs.ContainsKey(ownerId))
{
_playerRigs[ownerId].Destroy();
}
}
private void EnsurePlayerRig() {
if (playerRig != null) return;
playerRig = GameObject.Instantiate(rigBundle.LoadAsset<GameObject>("PlayerRep"));
playerRig.name = "PlayerRep";
body = playerRig.GetComponentInChildren<SLZ_Body>();
characterAnimationManager = playerRig.GetComponentInChildren<CharacterAnimationManager>();
animator = playerRig.GetComponentInChildren<Animator>();
headTransform = playerRig.transform.Find("Head");
leftHandTransform = playerRig.transform.Find("Hand (left)");
rightHandTransform = playerRig.transform.Find("Hand (right)");
body.OnStart();
}
public void UpdateIK(float deltaTime)
{
// Catch errors so other players arent broken
if (body == null || characterAnimationManager == null || animator == null || playerRig == null) return;
animator.Update(deltaTime);
characterAnimationManager.OnLateUpdate();
Vector3 repInputVel = Vector3.zero;
body.FullBodyUpdate(repInputVel, Vector3.zero);
body.ArtToBlender.UpdateBlender();
}
private PlayerRig(ulong id)
{
_playerRigs.Add(id, this);
_ownerId = id;
}
public static void OnPlayerSync(PlayerSyncMessage playerSyncMessage)
{
//SyncLogger.Msg("Player Sync Received " + playerSyncMessage.senderId);
PlayerRig playerRig = GetPlayerRig(playerSyncMessage.senderId);
if (playerRig == null) return;
playerRig.UpdatePlayerSync(playerSyncMessage.playerSyncInfo);
}
public void Destroy() public void Destroy()
{ {
_playerRigs.Remove(this); _playerRigs.Remove(_ownerId);
GameObject.Destroy(playerRig); GameObject.Destroy(playerRig);
} }
} }

View File

@@ -1,249 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using MelonLoader;
using StressLevelZero.Interaction;
using StressLevelZero.Pool;
using UnityEngine.Experimental.PlayerLoop;
using BoneSync.Networking.Messages;
using BoneSync.Networking;
using StressLevelZero.Data;
using System.Collections;
namespace BoneSync.Sync.Components
{
public static class TransformExtensions
{
public static string GetPath(this Transform current)
{
if (current.parent == null)
return "/" + current.name;
return current.parent.GetPath() + "/" + current.name;
}
public static Transform FromPath(string path)
{
Transform current = null;
foreach (string name in path.Split('/'))
{
if (current == null)
{
current = GameObject.Find(name).transform;
}
else
{
current = current.Find(name);
}
}
return current;
}
}
[RegisterTypeInIl2Cpp]
public class Syncable : MonoBehaviour
{
public const int SYNC_FPS = 20;
public static List<Syncable> syncablesCache = new List<Syncable>();
public Syncable(IntPtr intPtr) : base(intPtr) {
syncablesCache.Add(this);
}
private bool _syncCoroutineRunning;
public ulong _ownerId
{
private set;
get;
}
private ushort _syncId;
private ulong _lastSyncTime;
private bool _attemptedRegister;
public bool Registered => _syncId != 0;
public bool isStale => Time.time - _lastSyncTime > 5f;
public bool isOwner => _ownerId == BoneSync.lobby.GetLocalId();
public ushort GetSyncId() => _syncId;
public void SetSyncId(ushort id)
{
_syncId = id;
ObjectSyncCache.UpdateSyncId(this);
}
public InteractableHost interactableHost;
public InteractableHostManager interactableManager;
public Poolee poolee;
private Rigidbody[] rigidbodies;
private Transform[] transforms;
private void SetKinematic(bool kinematic)
{
foreach (Rigidbody rb in rigidbodies)
{
rb.isKinematic = kinematic;
}
}
public ObjectSyncTransform[] GetObjectSyncTransforms()
{
ObjectSyncTransform[] objectSyncTransforms = new ObjectSyncTransform[transforms.Length];
for (int i = 0; i < transforms.Length; i++)
{
objectSyncTransforms[i] = new ObjectSyncTransform()
{
transform = new SimpleTransform(transforms[i]),
velocity = Vector3.zero
};
}
return objectSyncTransforms;
}
public void ApplyObjectSyncTransforms(ObjectSyncTransform[] objectSyncTransforms)
{
if (objectSyncTransforms.Length != transforms.Length)
{
MelonLogger.Warning("ObjectSyncTransforms length mismatch: " + objectSyncTransforms.Length + " != " + transforms.Length);
return;
}
for (int i = 0; i < objectSyncTransforms.Length; i++)
{
ObjectSyncTransform objectSyncTransform = objectSyncTransforms[i];
transforms[i].ApplySimpleTransform(objectSyncTransform.transform);
}
}
public async Task SyncCoroutineAsync()
{
MelonLogger.Msg("Running sync coroutine for: " + gameObject.name);
if (_syncCoroutineRunning) return;
_syncCoroutineRunning = true;
while (isOwner)
{
MelonLogger.Msg("Sending object sync message for: " + gameObject.name);
ObjectSync.SendObjectSyncMessage(this);
await Task.Delay( GetSyncId() == 0 ? 1000 : 1000 / SYNC_FPS);
}
_syncCoroutineRunning = false;
return;
}
public void OnEnable()
{
FindComponents();
MelonLogger.Msg("Syncable enabled: " + transform.GetPath());
}
private void UpdateTransformList()
{
if (interactableManager)
{
transforms = interactableManager.hosts.Select(host => host.transform).ToArray();
}
else if (interactableHost)
{
transforms = new Transform[] { interactableHost.transform };
}
else if (poolee)
{
transforms = new Transform[] { poolee.transform };
}
}
public string GetSyncableWorldPath()
{
if (poolee && poolee.pool)
{
return "";
}
if (interactableHost || interactableManager)
{
return transform.GetPath();
}
return "";
}
public void FindComponents()
{
ObjectSyncCache.RemoveSyncable(this);
interactableManager = GetComponent<InteractableHostManager>();
interactableHost = GetComponent<InteractableHost>();
poolee = GetComponent<Poolee>();
rigidbodies = GetComponentsInChildren<Rigidbody>();
UpdateTransformList();
ObjectSyncCache.AddSyncable(this);
}
public bool ShouldSync()
{
FindComponents();
if (interactableManager && interactableManager.hosts.Count > 0)
{
return true;
}
if (interactableHost && interactableHost.hasRigidbody)
{
return true;
}
return false;
}
public void DiscardSyncable()
{
ObjectSyncCache.RemoveSyncable(this);
Destroy(this);
}
public void OnDisable()
{
if (!Registered)
{
DiscardSyncable();
return;
}
MelonLogger.Warning("Registered Syncable disabled: " + transform.GetPath());
}
public void SetOwner(ulong ownerId)
{
_ownerId = ownerId;
MelonLogger.Msg("Set owner for " + gameObject.name + " to " + ownerId);
_ = SyncCoroutineAsync();
UpdateKinematic();
}
private void UpdateKinematic()
{
SetKinematic(_ownerId != BoneSync.lobby.GetLocalId() && Registered);
}
private void _SendRegisterSync()
{
MelonLogger.Msg("Registering syncable object: " + gameObject.name);
SetOwner(BoneSync.lobby.GetLocalId());
SetSyncId(ObjectSync.SendRegisterSyncableMessage(this));
}
public void RegisterSyncable()
{
if (!BoneSync.lobby.IsConnected()) return;
if (_attemptedRegister) return;
if (Registered) return;
if (!ShouldSync()) return;
_attemptedRegister = true;
_SendRegisterSync();
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BoneSync.Networking.Messages;
using UnityEngine;
namespace BoneSync.Sync.Components
{
public partial class Syncable : MonoBehaviour
{
private AISyncInfo _aiSyncInfo;
private float _lastAISyncTime;
private const float AI_SYNC_FPS = 5f;
private void TrySendAISync()
{
if (!aiBrain) return;
if (Time.realtimeSinceStartup - _lastAISyncTime > 1 / AI_SYNC_FPS)
{
_SendAIStateSync();
}
}
private void _SendAIStateSync()
{
if (!aiBrain) return;
_lastAISyncTime = Time.realtimeSinceStartup;
AISyncInfo aiSyncInfo = new AISyncInfo(_syncId, aiBrain);
AISyncMessage message = new AISyncMessage(aiSyncInfo);
message.Broadcast();
}
private void _ApplyAISyncInfo(AISyncInfo aiSyncInfo)
{
if (!aiBrain) return;
aiBrain.behaviour.health.cur_hp = aiSyncInfo.health.cur_hp;
aiBrain.behaviour.health.cur_arm_lf = aiSyncInfo.health.cur_arm_lf;
aiBrain.behaviour.health.cur_arm_rt = aiSyncInfo.health.cur_arm_rt;
aiBrain.behaviour.health.cur_leg_lf = aiSyncInfo.health.cur_leg_lf;
aiBrain.behaviour.health.cur_leg_rt = aiSyncInfo.health.cur_leg_rt;
if (aiSyncInfo.health.cur_hp <= 0 && !aiBrain.puppetMaster.isDead)
{
aiBrain.puppetMaster.Kill();
}
else if (aiSyncInfo.health.cur_hp > 0 && aiBrain.puppetMaster.isDead)
{
aiBrain.puppetMaster.Resurrect();
}
aiBrain.puppetMaster.activeState = aiSyncInfo.puppetState;
aiBrain.puppetMaster.activeMode = aiSyncInfo.puppetMode;
aiBrain.behaviour.mentalState = aiSyncInfo.mentalState;
}
public void OnAISyncData(AISyncInfo aiSyncInfo)
{
_aiSyncInfo = aiSyncInfo;
_ApplyAISyncInfo(_aiSyncInfo);
}
}
}

View File

@@ -0,0 +1,408 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using MelonLoader;
using StressLevelZero.Interaction;
using StressLevelZero.Pool;
using UnityEngine.Experimental.PlayerLoop;
using BoneSync.Networking.Messages;
using BoneSync.Networking;
using StressLevelZero.Data;
using System.Collections;
using StressLevelZero.Props;
using BoneSync.Patching;
using StressLevelZero.Props.Weapons;
using StressLevelZero.AI;
using PuppetMasta;
using UnityEngine.SceneManagement;
using BoneSync.Data;
using StressLevelZero.Environment;
namespace BoneSync.Sync.Components
{
public static class TransformExtensions
{
public static string GetPath(this Transform current)
{
if (current.parent == null)
return "/" + current.name;
return current.parent.GetPath() + "/" + current.name;
}
public static Transform TransformFromPath(string path)
{
if (path.StartsWith("/"))
{
path = path.Substring(1);
}
string[] pathParts = path.Split('/');
Transform current = null;
foreach (string part in pathParts)
{
if (current == null)
{
current = GameObject.Find(part).transform;
}
else
{
current = current.Find(part);
}
}
return current;
}
}
[RegisterTypeInIl2Cpp]
public partial class Syncable : MonoBehaviour
{
public const int OBJECT_SYNC_FPS = 20;
public static Dictionary<int, Syncable> syncablesCache = new Dictionary<int, Syncable>();
public Syncable(IntPtr intPtr) : base(intPtr) {
syncablesCache[gameObject.GetHashCode()] = this;
}
private bool _syncCoroutineRunning;
private bool _ownerCoroutineRunning;
public ulong _ownerId
{
private set;
get;
}
private ushort _syncId;
private float _lastObjectSyncTime;
private bool _isInHolster;
private bool _attemptedRegister;
public bool Registered => _syncId != 0;
public bool isStale => Time.realtimeSinceStartup - _lastObjectSyncTime > 30f;
public bool isOwner => _ownerId == BoneSync.lobby.GetLocalId();
public bool isValid
{
get
{
if (_isInvalidCached) return false;
bool valid = _GetIsValid();
if (valid) return true;
_isInvalidCached = true;
return false;
}
}
private bool _isInvalidCached = false;
private bool _GetIsValid()
{
try
{
if (this == null) return false;
if (gameObject == null) return false;
return true;
} catch
{
return false;
}
}
public void SetInHolster(bool val)
{
SyncLogger.Msg(transform.GetPath() + " hosterState:" + val);
_isInHolster = val;
FindAndUpdateComponents();
}
public bool IsHolding()
{
if (interactableManager)
{
return interactableManager.grabbedHosts.Count > 0;
}
if (interactableHost)
{
return interactableHost.isAttached;
}
return false;
}
public InteractableHost interactableHost { private set; get; }
public InteractableHostManager interactableManager { private set; get; }
public Poolee poolee { private set; get; }
private Prop_Health propHealth;
private ObjectDestructable objectDestructable;
private Rigidbody[] rigidbodies;
private Transform[] _transforms;
private Gun gun;
private Magazine magazine;
private AlignPlug[] plugs;
private Socket[] sockets;
private AIBrain aiBrain;
private PullDevice pullDevice;
private ButtonToggle[] buttonToggles;
private SpawnFragment spawnFragment;
private SpawnFragmentArray spawnFragmentArray;
private Powerable powerable;
private Cart physicsCart;
private void CheckAutoSync()
{
if (!isValid) return;
FindAndUpdateComponents();
if (ShouldAutoSync())
{
SyncLogger.Msg("AutoSyncing: " + transform.GetPath());
RegisterSyncable();
}
}
private IEnumerator _OnEnableCo()
{
// wait for a couple frames to make sure all components are initialized, I hate this but it works
yield return null;
yield return null;
yield return null;
CheckAutoSync();
yield return new WaitForSeconds(SceneSync.MAP_LOAD_GRACE_PERIOD);
CheckAutoSync(); // check again after grace period
}
public void OnEnable()
{
syncablesCache[gameObject.GetHashCode()] = this;
FindAndUpdateComponents();
MelonCoroutines.Start(_OnEnableCo());
}
public bool ShouldAutoSync()
{
if (SceneSync.TimeSinceLastSceneChange < SceneSync.MAP_LOAD_GRACE_PERIOD) return false; // don't sync if scene just changed, to prevent some weird stuff that happens when a level is loaded
if (!BoneSync.lobby.IsHost && !ClientSpawningAllowed()) return false;
if (InPoolManagerTransform()) return false;
if (!gameObject.activeInHierarchy) return false;
if (poolee && poolee.pool) {
return true;
}
if (buttonToggles.Length > 0 && rigidbodies.Length == 0) return true;
if (physicsCart != null) return true;
return false;
}
public string GetSyncableWorldPath()
{
if (!isValid) return "";
if (transform == null) return "";
if (poolee && poolee.pool)
{
return "";
}
if (interactableHost || interactableManager)
{
return transform.GetPath();
}
if (rigidbodies?.Length == 0 && buttonToggles?.Length > 0)
{
return transform.GetPath();
}
return "";
}
public void FindAndUpdateComponents()
{
if (!isValid) return;
ObjectSyncCache.RemoveSyncable(this);
pullDevice = GetComponent<PullDevice>();
interactableManager = GetComponent<InteractableHostManager>();
interactableHost = GetComponent<InteractableHost>();
poolee = GetComponent<Poolee>();
propHealth = GetComponent<Prop_Health>();
objectDestructable = GetComponent<ObjectDestructable>();
gun = GetComponent<Gun>();
magazine = GetComponent<Magazine>();
sockets = GetComponentsInChildren<Socket>();
aiBrain = GetComponent<AIBrain>();
buttonToggles = GetComponentsInChildren<ButtonToggle>();
physicsCart = GetComponent<Cart>();
if (sockets.Length == 0)
{
plugs = GetComponentsInChildren<AlignPlug>();
} else {
plugs = new AlignPlug[0]; // don't use plugs if sockets are present
}
spawnFragment = GetComponent<SpawnFragment>();
spawnFragmentArray = GetComponent<SpawnFragmentArray>();
UpdateTransformList();
TryPatchUnityEvents();
ObjectSyncCache.AddSyncable(this);
}
private void ResetSyncStatus()
{
_ownerId = 0;
_syncId = 0;
_attemptedRegister = false;
SetKinematic(false);
}
private bool InPoolManagerTransform()
{
return (transform.root.name == "Pool Manager");
}
public bool CanBeSynced()
{
FindAndUpdateComponents();
if (spawnFragment) return false; // if has spawn fragment, don't sync
if (spawnFragmentArray) return false; // if has spawn fragment array, don't sync
if (rigidbodies?.Length > 0) return true;
if (ShouldAutoSync()) return true;
return false;
}
private IEnumerator DespawnSyncable()
{
for (int i = 0; i < 3; i++)
{
gameObject?.SetActive(false);
yield return null;
}
yield break;
}
public bool IsPlugged()
{
if (plugs.Length == 0) return false;
for (int i = 0; i < plugs.Length; i++)
{
AlignPlug alignPlug = plugs[i];
if (alignPlug.GetSocket()) return true;
if (alignPlug._isExitTransition || alignPlug._isEnterTransition) return true; // if plug is in transition, consider it plugged
}
return false;
}
private void _DiscardSyncable(bool force, bool despawn)
{
if (!isValid) return; // if object is destroyed, don't do anything
bool isRegistered = Registered;
//SyncLogger.Debug("Discarding syncable: " + transform.GetPath() + " force:" + force + " registered:" + isRegistered + " despawn:" + despawn + " isPlugged:" + IsPlugged());
if (isRegistered)
{
bool isPlugged = IsPlugged();
bool canDiscard = isOwner && (!isPlugged || force); // only owner can discard
SyncLogger.Debug("Discarding syncable: " + transform.GetPath() + " force:" + force + " registered:" + isRegistered + " despawn:" + despawn + " isPlugged:" + isPlugged + " canDiscard:" + canDiscard);
//SyncLogger.Warning("Discarding registered syncable: " + transform.GetPath() + " force: " + force);
if (canDiscard)
{
_SendDiscard(true); // owner sends discard message
}
if (!canDiscard && !force) return;
}
//SyncLogger.Msg("Discarding syncable: " + transform.GetPath() + " force:" + force + " registered:" + isRegistered + " despawn:" + despawn + " isPlugged:" + IsPlugged());
try
{
EjectAllPlugs(true);
}
catch {
SyncLogger.Warning("Failed to eject plugs (should be fine)");
}
if (gameObject) syncablesCache.Remove(gameObject.GetHashCode());
ObjectSyncCache.RemoveSyncable(this);
ResetSyncStatus();
Destroy(this); // delete the component
if (despawn) MelonCoroutines.Start(DespawnSyncable());
}
public void OnDestroy()
{
if (Registered)
{
SyncLogger.Warning("Destroying registered syncable: " + transform.GetPath());
}
DiscardSyncableImmediate(true, Registered);
//SyncLogger.Msg("Syncable destroyed: " + transform.GetPath());
}
private IEnumerator _FlagForDiscardCo(bool force, bool despawn) {
yield return null;
yield return null;
_DiscardSyncable(force, despawn);
} // delay discard to prevent silly behavior
public void DiscardSyncable(bool force = false, bool despawn = false)
{
if (Registered && isOwner)
{
DiscardSyncableImmediate(force, despawn);
return;
}
MelonCoroutines.Start(_FlagForDiscardCo(force, despawn));
}
public void DiscardSyncableImmediate(bool force = false, bool despawn = false)
{
_DiscardSyncable(force, despawn);
}
public void OnDisable()
{
if (Registered && !isOwner)
{
SyncLogger.Warning("tried to disable non-owner syncable: " + transform.GetPath());
gameObject.SetActive(true);
return;
}
DiscardSyncable();
}
public void RegisterSyncable()
{
if (!BoneSync.IsConnected) return;
FindAndUpdateComponents();
if (Registered)
{
TryBecomeOwner();
return;
}
if (_attemptedRegister) return;
if (!CanBeSynced()) return;
_attemptedRegister = true;
_SendRegisterSync();
}
}
}

View File

@@ -0,0 +1,87 @@
using BoneSync.Data;
using BoneSync.Networking.Messages;
using BoneSync.Patching;
using MelonLoader;
using StressLevelZero.Data;
using StressLevelZero.Props;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace BoneSync.Sync.Components
{
public partial class Syncable : MonoBehaviour
{
//private LootTableData originalLootTableData = null;
private Dictionary<int, LootTableData> originalLootTableData = new Dictionary<int, LootTableData>();
private void _UpdateLootTable(ObjectDestructable destructable)
{
if (!destructable) return;
if (!originalLootTableData.ContainsKey(destructable.GetHashCode()))
{
originalLootTableData[destructable.GetHashCode()] = destructable.lootTable;
}
if (!BoneSync.IsConnected || BoneSync.lobby.IsHost)
{
if (originalLootTableData[destructable.GetHashCode()] != null)
{
destructable.lootTable = originalLootTableData[destructable.GetHashCode()];
}
}
else
{
destructable.lootTable = null;
}
}
private void SetHealth(float health, int hits = 0)
{
if (gameObject.activeSelf == false && health > 0)
{
gameObject.SetActive(true);
}
if (propHealth)
{
propHealth.cur_Health = health;
propHealth.hits = hits;
}
if (objectDestructable)
{
_UpdateLootTable(objectDestructable);
objectDestructable._health = health;
objectDestructable._hits = hits;
}
}
public void Damage(ObjectDamageInfo damageInfo)
{
ObjectHealthInfo healthInfo = damageInfo.objectHealthInfo;
SetHealth(healthInfo._health, healthInfo._hits); // always set health first
ObjectDamageType eventType = damageInfo.eventType;
if (eventType == ObjectDamageType.SyncHealth) return;
if (eventType == ObjectDamageType.DestructibleTakeDamage && objectDestructable)
{
SyncLogger.Msg("NetworkDestructableTakeDamage: " + healthInfo.damage);
CallPatchedMethods.TakeDamage(objectDestructable, healthInfo.normal, healthInfo.damage, healthInfo.crit, healthInfo.attackType);
return;
}
if (eventType == ObjectDamageType.PropHealthTakeDamage && propHealth)
{
SyncLogger.Msg("NetworkPropHealthTakeDamage: " + healthInfo.damage);
CallPatchedMethods.TakeDamage(propHealth, healthInfo.damage, healthInfo.crit, healthInfo.attackType);
return;
}
}
}
}

View File

@@ -0,0 +1,227 @@
using BoneSync.Data;
using BoneSync.Networking.Messages;
using BoneSync.Patching;
using MelonLoader;
using StressLevelZero.Zones;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace BoneSync.Sync.Components
{
// SyncableNetworking.cs
public partial class Syncable : MonoBehaviour
{
private float _syncInterval = 0f;
private Queue<byte[]> _simpleEventQueue = new Queue<byte[]>();
private void SendObjectSync()
{
_lastObjectSyncTime = Time.realtimeSinceStartup;
ObjectSync.SendObjectSyncMessage(this);
}
private IEnumerator OwnerCoroutineAsync()
{
if (_ownerCoroutineRunning) yield break;
SyncLogger.Msg("Running owner coroutine for: " + _syncId);
_ownerCoroutineRunning = true;
while (isOwner)
{
if (!_syncCoroutineRunning)
{
RunSyncLoop(); // start sync loop if it's not running
}
yield return new WaitForSeconds(_syncInterval * 10f); // 10x sync interval
}
_ownerCoroutineRunning = false;
SyncLogger.Msg("Owner coroutine ended for: " + _syncId);
yield break;
}
public IEnumerator SyncCoroutineAsync()
{
if (_syncCoroutineRunning) yield break;
if (!ShouldSendSync()) yield break;
SyncLogger.Msg("Starting sync coroutine for: " + transform.GetPath());
_syncCoroutineRunning = true;
_syncInterval = 1 / OBJECT_SYNC_FPS; // micro optimization to avoid division every frame, propably makes no difference
while (ShouldSendSync())
{
try
{
SendObjectSync();
TrySendPlugSync();
TrySendAISync();
TrySendAttributeSync();
} catch (Exception e)
{
MelonLogger.Error("Error in sync coroutine", e);
}
yield return new WaitForSeconds(_syncInterval);
}
SyncLogger.Msg("Ending sync coroutine for: " + transform.GetPath());
_syncCoroutineRunning = false;
yield break;
}
private void RunSyncLoop()
{
MelonCoroutines.Start(SyncCoroutineAsync());
}
public void SetOwner(ulong ownerId)
{
SyncLogger.Msg("Setting owner for " + _syncId + " to " + ownerId);
_ownerId = ownerId;
//FindAndUpdateComponents();
UpdateKinematic();
RunSyncLoop();
TryCatchUpSimpleEvents();
MelonCoroutines.Start(OwnerCoroutineAsync());
}
public bool ClientSpawningAllowed()
{
if (poolee && poolee.pool)
{
return PoolBlacklist.IsClientSpawnPool(poolee.pool);
}
return false;
}
private IEnumerator __SendRegisterSyncCo()
{
yield return null; // wait a frame
SetSyncId(ObjectSync.SendRegisterSyncableMessage(this));
SetOwner(BoneSync.lobby.GetLocalId());
yield break;
}
private void _SendRegisterSync()
{
SyncLogger.Msg("Registering syncable object: " + gameObject.name);
MelonCoroutines.Start(__SendRegisterSyncCo());
}
private void _SendDiscard(bool despawn = false)
{
SyncLogger.Msg("Sending discard for " + _syncId + " despawn: " + despawn);
DiscardSyncableMessageData discardSyncableMessageData = new DiscardSyncableMessageData()
{
syncId = _syncId,
despawn = despawn
};
DiscardSyncableMessage discardSyncableMessage = new DiscardSyncableMessage(discardSyncableMessageData);
discardSyncableMessage.Broadcast();
}
public void OnOwnershipTransferRequest(ulong newOwnerId)
{
//SyncLogger.Msg("Ownership transfer request for " + _syncId + " to " + newOwnerId);
if (isOwner && !IsHolding() && !_isInHolster)
{
SyncLogger.Msg("Sending ownership transfer for " + _syncId + " to " + newOwnerId);
OwnershipTransferMessageData data = new OwnershipTransferMessageData()
{
syncId = _syncId,
NewOwnerId = newOwnerId,
force = true
};
OwnershipTransferMessage message = new OwnershipTransferMessage(data);
message.Broadcast();
SetOwner(newOwnerId);
}
}
public void TryBecomeOwner(bool force = false)
{
if (Registered && !isOwner)
{
ulong localId = BoneSync.lobby.GetLocalId();
SyncLogger.Msg("Attempting to become owner of " + _syncId + " force: " + force);
OwnershipTransferMessageData data = new OwnershipTransferMessageData()
{
syncId = _syncId,
NewOwnerId = localId,
force = force
};
OwnershipTransferMessage message = new OwnershipTransferMessage(data);
message.Broadcast();
if (force)
{
SetOwner(localId);
}
}
RunSyncLoop();
}
public bool ShouldSendSync()
{
if (!Registered) return false;
if (!isOwner) return false;
if (isStale) return true;
if (_isInHolster) return true;
if (AllRigidbodiesSleeping()) return false;
return true;
}
public bool AddSimpleEventToQueue(SimpleEventType eType, byte index = 0, byte len = 0, byte[] extraData = null)
{
RegisterSyncable();
if (!isOwner && Registered) { return false; }
_simpleEventQueue.Enqueue(new byte[] { (byte)eType, index, len }.Concat(extraData ?? new byte[0]).ToArray());
TryCatchUpSimpleEvents();
return true;
}
private void TryCatchUpSimpleEvents()
{
if (Registered && !isOwner)
{
_simpleEventQueue.Clear();
SyncLogger.Debug("Clearing simple event queue for " + _syncId);
return;
}
if (_simpleEventQueue.Count > 0 && Registered && isOwner)
{
byte[] eventData = _simpleEventQueue.Dequeue();
_SendSimpleEvent((SimpleEventType)eventData[0], eventData[1], eventData[2], eventData.Skip(3).ToArray());
TryCatchUpSimpleEvents();
}
}
private void _SendSimpleEvent(SimpleEventType eType, byte index = 0, byte len = 0, byte[] extraData = null)
{
SyncLogger.Msg("Sending simple event: " + eType);
SimpleSyncableEvent data = new SimpleSyncableEvent()
{
syncId = _syncId,
eventType = eType,
index = index,
length = len,
extraData = extraData
};
SimpleSyncableEventMessage simpleSyncEvent = new SimpleSyncableEventMessage(data);
simpleSyncEvent.Broadcast();
}
private void DestroyZoneTrackers()
{
/*ZoneTracker[] zoneTrackers = GetComponentsInChildren<ZoneTracker>(true);
foreach (ZoneTracker zoneTracker in zoneTrackers)
{
Destroy(zoneTracker);
}*/
}
public ushort GetSyncId() => _syncId;
public void SetSyncId(ushort id)
{
_syncId = id;
ObjectSyncCache.UpdateSyncId(this);
if (id != 0)
{
DestroyZoneTrackers();
}
}
}
}

View File

@@ -0,0 +1,308 @@
using BoneSync.Data;
using BoneSync.Networking;
using BoneSync.Networking.Messages;
using BoneSync.Patching;
using Facepunch.Steamworks;
using MelonLoader;
using StressLevelZero.Interaction;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;
namespace BoneSync.Sync.Components
{
public partial class Syncable : MonoBehaviour
{
private HashSet<int> patchedButtonToggles = new HashSet<int>();
private bool pullDevicePatched = false;
bool OnButtonEvent(ButtonToggle toggle, SimpleEventType eventType)
{
SyncLogger.Msg("ButtonToggle:" + eventType + " " + toggle.transform.GetPath());
byte index = (byte)Array.IndexOf(buttonToggles, toggle);
bool isPressed = toggle._isPressed;
bool hasBeenPressed = toggle._hasBeenPressed;
byte[] buttonState = BitPacking.PackBits(new bool[] { isPressed, hasBeenPressed });
return AddSimpleEventToQueue(eventType, index, (byte)buttonToggles.Length, buttonState);
}
bool ButtonOnPress(ButtonToggle toggle)
{
return OnButtonEvent(toggle, SimpleEventType.OnButtonPress);
}
bool ButtonOnRelease(ButtonToggle toggle)
{
return OnButtonEvent(toggle, SimpleEventType.OnButtonRelease);
}
bool ButtonOnOneShot(ButtonToggle toggle)
{
return OnButtonEvent(toggle, SimpleEventType.OnButtonOneShot);
}
bool DeviceOnPull(PullDevice device)
{
return AddSimpleEventToQueue(SimpleEventType.OnDevicePull);
}
bool DeviceOnRelease(PullDevice device)
{
return AddSimpleEventToQueue(SimpleEventType.OnDeviceRelease);
}
private void TryPatchUnityEvents()
{
try
{
_TryPatchPullDevice();
foreach (ButtonToggle buttonToggle in GetComponentsInChildren<ButtonToggle>())
{
_TryPatchButtonToggle(buttonToggle);
}
} catch (Exception e)
{
SyncLogger.Error("Failed to patch UnityEvents: " + e);
}
}
private void _TryPatchPullDevice()
{
if (pullDevicePatched) return;
if (!pullDevice) return;
//pullDevice.OnHandlePull.AddListener((UnityAction)DeviceOnPull);
//pullDevice.OnHandleReturn.AddListener((UnityAction)DeviceOnRelease);
pullDevice.OnHandlePull =UnityEventPatch<PullDevice>.Patch(pullDevice, pullDevice.OnHandlePull, DeviceOnPull).GetPatchedEvent();
pullDevice.OnHandleReturn = UnityEventPatch<PullDevice>.Patch(pullDevice, pullDevice.OnHandleReturn, DeviceOnRelease).GetPatchedEvent();
pullDevicePatched = true;
}
private void _TryPatchButtonToggle(ButtonToggle buttonToggle)
{
if (patchedButtonToggles.Contains(buttonToggle.GetHashCode())) return;
SyncLogger.Msg("Patching ButtonToggle: " + buttonToggle.transform.GetPath());
//buttonToggle.onPress.AddListenerWithArgs<ButtonToggle>((btn, args) => ButtonOnPress(btn), buttonToggle);
//buttonToggle.onDepress.AddListenerWithArgs<ButtonToggle>((btn, args) => ButtonOnRelease(btn), buttonToggle);
buttonToggle.onPress = UnityEventPatch<ButtonToggle>.Patch(buttonToggle, buttonToggle.onPress, ButtonOnPress).GetPatchedEvent();
buttonToggle.onDepress = UnityEventPatch<ButtonToggle>.Patch(buttonToggle, buttonToggle.onDepress, ButtonOnRelease).GetPatchedEvent();
buttonToggle.onPressOneShot = UnityEventPatch<ButtonToggle>.Patch(buttonToggle, buttonToggle.onPressOneShot, ButtonOnOneShot).GetPatchedEvent();
patchedButtonToggles.Add(buttonToggle.GetHashCode());
}
public bool AllRigidbodiesSleeping()
{
if (rigidbodies.Length == 0) return false;
foreach (Rigidbody rb in rigidbodies)
{
try
{
if (!rb.IsSleeping()) return false;
}
catch { } // ignore null rigidbodies
}
return true;
}
private void _SetKinematic(bool kinematic)
{
if (rigidbodies.Length == 0) return;
foreach (Rigidbody rb in rigidbodies)
{
try
{
rb.isKinematic = kinematic;
}
catch { } // ignore null rigidbodies
}
}
private void SetKinematic(bool kinematic)
{
if (!isValid) return;
try
{
_SetKinematic(kinematic);
}
catch {
SyncLogger.Warning("Failed to set kinematic");
}
}
public ObjectSyncTransform[] GetObjectSyncTransforms()
{
ObjectSyncTransform[] objectSyncTransforms = new ObjectSyncTransform[rigidbodies.Length];
for (int i = 0; i < _transforms.Length; i++)
{
objectSyncTransforms[i] = new ObjectSyncTransform()
{
transform = new SimpleSyncTransform(_transforms[i]),
velocity = rigidbodies[i].velocity,
};
}
return objectSyncTransforms;
}
public void ApplyObjectSyncTransforms(ObjectSyncTransform[] objectSyncTransforms)
{
_lastObjectSyncTime = Time.realtimeSinceStartup;
if (IsPlugged()) { return; }
if (objectSyncTransforms.Length != rigidbodies.Length)
{
SyncLogger.Warning("ObjectSyncTransforms length mismatch: " + objectSyncTransforms.Length + " != " + _transforms.Length);
return;
}
for (int i = 0; i < objectSyncTransforms.Length; i++)
{
ObjectSyncTransform objectSyncTransform = objectSyncTransforms[i];
Rigidbody rb = rigidbodies[i];
rb.angularVelocity = objectSyncTransform.angularVelocity;
rb.velocity = objectSyncTransform.velocity;
//rb.MovePosition(objectSyncTransform.transform.position);
//rb.MoveRotation(objectSyncTransform.transform.rotation);
rb.position = objectSyncTransform.transform.position;
rb.rotation = objectSyncTransform.transform.rotation;
if (objectSyncTransform.transform.scale.HasValue)
{
_transforms[i].localScale = objectSyncTransform.transform.scale.Value;
}
//_transforms[i].ApplySimpleTransform(objectSyncTransform.transform);
}
}
private Syncable GetRigidbodySyncable(Rigidbody rigidbody)
{
if (rigidbody == null) return null;
return rigidbody.GetComponentInParent<Syncable>();
}
private bool RigidbodyBelongsToSyncable(Rigidbody rigidbody)
{
return GetRigidbodySyncable(rigidbody) == this;
}
private Rigidbody[] GetRigidbodies()
{
if (interactableManager)
{
return interactableManager.hosts.Select(host => host.rb).ToArray();
}
if (interactableHost)
{
return new Rigidbody[] { interactableHost.rb };
}
if (poolee)
{
return poolee.GetComponentsInChildren<Rigidbody>(true);
}
if (physicsCart)
{
return new Rigidbody[] { physicsCart.rb };
}
return new Rigidbody[0];
}
private void UpdateTransformList()
{
// get non-null rigidbodies
Rigidbody[] rbs = GetRigidbodies().Where(rb => rb != null).ToArray();
rigidbodies = rbs;
_transforms = rbs.Select(rb => rb.transform).ToArray();
}
private void UpdateKinematic()
{
//SetKinematic(false);
SetKinematic(_ownerId != BoneSync.lobby.GetLocalId() && Registered && !IsPlugged());
}
internal void OnSimpleSyncableEvent(SimpleSyncableEvent eventData)
{
SyncLogger.Msg("OnSimpleSyncableEvent: " + eventData.eventType);
SimpleEventType eType = eventData.eventType;
byte index = eventData.index;
byte len = eventData.length;
switch (eType) {
case SimpleEventType.OnDevicePull:
pullDevice?.OnHandlePull?.BypassPatchInvoke();
break;
case SimpleEventType.OnDeviceRelease:
pullDevice?.OnHandleReturn?.BypassPatchInvoke();
break;
case SimpleEventType.OnButtonPress:
if (len != eventData.length) { SyncLogger.Warning("ButtonPress length mismatch: " + len + " != " + eventData.length); }
if (index < buttonToggles.Length)
{
ButtonToggle toggle = buttonToggles[index];
if (toggle == null)
{
SyncLogger.Warning("ButtonToggle at index " + index + " is null");
break;
}
bool[] buttonState = BitPacking.UnpackBits(eventData.extraData, 2);
toggle._isPressed = buttonState[0];
toggle._hasBeenPressed = buttonState[1];
toggle.onPress.BypassPatchInvoke();
}
break;
case SimpleEventType.OnButtonRelease:
if (len != eventData.length) { SyncLogger.Warning("ButtonPress length mismatch: " + len + " != " + eventData.length); }
if (index < buttonToggles.Length)
{
ButtonToggle toggle = buttonToggles[index];
if (toggle == null)
{
SyncLogger.Warning("ButtonToggle at index " + index + " is null");
break;
}
bool[] buttonState = BitPacking.UnpackBits(eventData.extraData, 2);
toggle._isPressed = buttonState[0];
toggle._hasBeenPressed = buttonState[1];
toggle.onDepress.BypassPatchInvoke();
}
break;
case SimpleEventType.OnButtonOneShot:
if (len != eventData.length) {
SyncLogger.Warning("ButtonPress length mismatch: " + len + " != " + eventData.length);
}
if (index < buttonToggles.Length)
{
ButtonToggle toggle = buttonToggles[index];
if (toggle == null)
{
SyncLogger.Warning("ButtonToggle at index " + index + " is null");
break;
}
bool[] buttonState = BitPacking.UnpackBits(eventData.extraData, 2);
toggle._isPressed = buttonState[0];
toggle._hasBeenPressed = buttonState[1];
toggle.onPressOneShot.BypassPatchInvoke();
}
break;
case SimpleEventType.CartGo:
CallPatchedMethods.CartGo(physicsCart);
break;
case SimpleEventType.CartGoBackwards:
CallPatchedMethods.CartGoBackward(physicsCart);
break;
case SimpleEventType.CartLaunch:
CallPatchedMethods.CartLaunch(physicsCart);
break;
case SimpleEventType.CartGoForward:
CallPatchedMethods.CartGoForward(physicsCart);
break;
case SimpleEventType.CartDrop:
//CallPatchedMethods.Drop();
break;
default:
SyncLogger.Warning("Unknown SimpleEventType: " + eType);
break;
}
}
// on collision
}
}

View File

@@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using MelonLoader;
using StressLevelZero.Interaction;
using BoneSync.Patching;
using BoneSync.Data;
namespace BoneSync.Sync.Components
{
public static class PlugExtensions
{
public static Socket GetSocket(this AlignPlug plug)
{
Socket socket = plug._lastSocket;
if (!socket) return null;
if (socket.LockedPlug == plug) return socket;
if (plug._isEnterTransition) return socket;
if (plug._isExitTransition) return socket;
return null;
}
public static bool SafeEject(this AlignPlug plug)
{
if (!plug.GetSocket()) return false;
plug.EjectPlug();
return true;
}
public static void ForceEject(this AlignPlug plug)
{
if (!plug.GetSocket()) return;
plug.EjectPlug();
plug.ClearFromSocket();
plug._isEnterTransition = false;
plug._isExitTransition = false;
plug._isExitComplete = true;
}
public static bool SafeInsert(this AlignPlug plug, Socket socket)
{
if (!socket) return false;
Plug lockedPlug = socket.LockedPlug;
if (lockedPlug && lockedPlug != plug)
{
AlignPlug alignPlug = lockedPlug.GetComponent<AlignPlug>();
if (alignPlug)
{
alignPlug.ForceEject();
} else
{
return false;
}
}
if (plug.GetSocket() != socket)
{
plug.ForceEject();
}
plug.InsertPlug(socket);
return true;
}
}
public partial class Syncable : MonoBehaviour
{
private float _lastPlugSyncTime;
private const float PLUG_SYNC_FPS = 1f;
private void TrySendPlugSync()
{
if (Time.realtimeSinceStartup - _lastPlugSyncTime > (1 / PLUG_SYNC_FPS))
{
_SendPlugSync();
}
}
private void _SendPlugSync()
{
if (!isOwner) return;
if (!Registered) return;
foreach (AlignPlug plug in plugs)
{
if (!plug) continue;
Socket socket = plug.GetSocket();
AlignPlugPatches.OnPlugSocketChange(plug, socket);
_lastPlugSyncTime = Time.realtimeSinceStartup;
}
}
public void EjectAllPlugs(bool force = false)
{
if (plugs == null) return;
foreach (AlignPlug plug in plugs)
{
if (plug == null) continue;
if (plug.GetSocket())
{
if (force)
{
plug.ForceEject();
}
else
{
plug.SafeEject();
}
}
}
}
public AlignPlug GetPlugFromId(byte id)
{
if (plugs.Length <= id)
{
SyncLogger.Error("Plug ID out of range");
return null;
}
return plugs[id];
}
public Socket GetSocketFromId(byte id)
{
if (sockets.Length <= id)
{
SyncLogger.Error("Socket ID out of range");
return null;
}
return sockets[id];
}
public byte GetSocketId(Socket socket)
{
for (byte i = 0; i < sockets.Length; i++)
{
if (sockets[i] == socket)
{
return i;
}
}
return 255;
}
public byte GetPlugId(AlignPlug plug)
{
for (byte i = 0; i < plugs.Length; i++)
{
if (plugs[i] == plug)
{
return i;
}
}
return 255;
}
}
}

View File

@@ -0,0 +1,98 @@
using BoneSync.Networking.Messages;
using BoneSync.Patching;
using StressLevelZero.Combat;
using StressLevelZero.Pool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace BoneSync.Sync.Components
{
public partial class Syncable : MonoBehaviour
{
private const float ATTRIBUTE_SYNC_FPS = 0.2f;
private float _lastAttributeSyncTime;
private void StripDamage(ref BulletObject bulletObject)
{
AmmoVariables ammoVariables = bulletObject.ammoVariables;
ammoVariables.AttackDamage = 0f;
bulletObject.ammoVariables = ammoVariables;
}
private void TrySendAttributeSync()
{
if (!Registered) return;
if (!isOwner) return;
if (Time.realtimeSinceStartup - _lastAttributeSyncTime > 1 / ATTRIBUTE_SYNC_FPS)
{
_SendAttributeSync();
}
}
private void _SendAttributeSync()
{
_SendMagazineData();
_lastAttributeSyncTime = Time.realtimeSinceStartup;
}
public void ApplyMagazineData(MagazineSyncData magazineSyncData)
{
if (!magazine) return; // If the magazine is null, return
MagazineData magazineData = magazineSyncData.magazineData;
if (!magazineData) return;
magazine.magazineData.AmmoSlots = magazineData.AmmoSlots;
magazine.magazineData.cartridgeType = magazineData.cartridgeType;
magazine.magazineData.weight = magazineData.weight;
magazine.magazineData.platform = magazineData.platform;
magazine.firstBullet?.SetActive(magazineData.AmmoSlots.Count > 0);
}
public MagazineSyncData GetMagazineData()
{
MagazineSyncData data = new MagazineSyncData();
data.syncId = GetSyncId();
data.magazineData = magazine.magazineData;
return data;
}
private void _SendMagazineData()
{
if (!magazine) return;
MagazineSyncData data = GetMagazineData();
MagazineSyncMessage message = new MagazineSyncMessage(data);
message.Broadcast();
}
public void OnWeaponSyncData(GunSyncInfo gunSyncInfo)
{
if (!gun) return;
gun.chamberedCartridge = gunSyncInfo.bulletObject;
gun.cartridgeState = gunSyncInfo.cartridgeState;
gun.hammerState = gunSyncInfo.hammerState;
if (gunSyncInfo.messageType == GunSyncMessageType.Fire)
{
gun.gunSFX?.GunShot();
Transform firepointTransform = gun.firePointTransform;
Vector3 position = firepointTransform.position;
Quaternion rotation = firepointTransform.rotation;
BulletObject bulletObject = gunSyncInfo.bulletObject;
StripDamage(ref bulletObject);
//gun.EjectCartridge();
PoolSpawner.SpawnProjectile(position, rotation, gunSyncInfo.bulletObject, "1911", null);
PoolSpawner.SpawnMuzzleFlare(position, rotation, PoolSpawner.MuzzleFlareType.Default);
return;
}
if (gunSyncInfo.messageType == GunSyncMessageType.EjectCartridge)
{
gun.EjectCartridge();
return;
}
}
}
}

View File

@@ -1,6 +1,11 @@
using BoneSync.Networking.Messages; using BoneSync.Data;
using BoneSync.Networking;
using BoneSync.Networking.Messages;
using BoneSync.Patching;
using BoneSync.Sync.Components; using BoneSync.Sync.Components;
using MelonLoader; using MelonLoader;
using StressLevelZero.Data;
using StressLevelZero.Environment;
using StressLevelZero.Interaction; using StressLevelZero.Interaction;
using StressLevelZero.Pool; using StressLevelZero.Pool;
using System; using System;
@@ -9,138 +14,11 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.SceneManagement;
using Random = UnityEngine.Random;
namespace BoneSync.Sync namespace BoneSync.Sync
{ {
public static class ObjectSyncCache
{
private static Dictionary<string, Syncable> _pathToSyncable = new Dictionary<string, Syncable>();
private static Dictionary<ushort, Syncable> _idToSyncable = new Dictionary<ushort, Syncable>();
private static Dictionary<Poolee, Syncable> _pooleeToSyncable = new Dictionary<Poolee, Syncable>();
private static Dictionary<InteractableHost, Syncable> _interactableHostToSyncable = new Dictionary<InteractableHost, Syncable>();
private static Dictionary<InteractableHostManager, Syncable> _interactableHostManagerToSyncable = new Dictionary<InteractableHostManager, Syncable>();
public static void AddSyncable(Syncable syncable)
{
string path = syncable.transform.GetPath();
if (path != null && path != "")
{
_pathToSyncable[path] = syncable;
}
if (syncable.GetSyncId() != 0)
{
_idToSyncable[syncable.GetSyncId()] = syncable;
}
if (syncable.interactableHost)
{
_interactableHostToSyncable[syncable.interactableHost] = syncable;
}
if (syncable.interactableManager)
{
_interactableHostManagerToSyncable[syncable.interactableManager] = syncable;
}
if (syncable.poolee)
{
_pooleeToSyncable[syncable.poolee] = syncable;
}
}
public static void RemoveSyncable(Syncable syncable)
{
if (syncable.transform)
{
_pathToSyncable.Remove(syncable.transform.GetPath());
}
if (syncable.GetSyncId() != 0)
{
_idToSyncable.Remove(syncable.GetSyncId());
}
if (syncable.interactableHost)
{
_interactableHostToSyncable.Remove(syncable.interactableHost);
}
if (syncable.interactableManager)
{
_interactableHostManagerToSyncable.Remove(syncable.interactableManager);
}
if (syncable.poolee)
{
_pooleeToSyncable.Remove(syncable.poolee);
}
}
public static void UpdateSyncId(Syncable syncable)
{
// remove other enties where value is the same
foreach (KeyValuePair<ushort, Syncable> entry in _idToSyncable)
{
if (entry.Value == syncable)
{
_idToSyncable.Remove(entry.Key);
}
}
ushort id = syncable.GetSyncId();
if (_idToSyncable.ContainsKey(id))
{
_idToSyncable.Remove(id);
}
_idToSyncable[id] = syncable;
}
public static Syncable GetSyncable(string path)
{
if (_pathToSyncable.ContainsKey(path))
{
return _pathToSyncable[path];
}
return null;
}
public static Syncable GetSyncable(ushort id)
{
if (_idToSyncable.ContainsKey(id))
{
return _idToSyncable[id];
}
return null;
}
public static Syncable GetSyncable(Poolee poolee)
{
if (_pooleeToSyncable.ContainsKey(poolee))
{
return _pooleeToSyncable[poolee];
}
return null;
}
public static Syncable GetSyncable(InteractableHost interactableHost)
{
if (_interactableHostToSyncable.ContainsKey(interactableHost))
{
return _interactableHostToSyncable[interactableHost];
}
return null;
}
public static Syncable GetSyncable(InteractableHostManager interactableHostManager)
{
if (_interactableHostManagerToSyncable.ContainsKey(interactableHostManager))
{
return _interactableHostManagerToSyncable[interactableHostManager];
}
return null;
}
public static void DISCARD_ALL_SYNCABLES() {
foreach (Syncable syncable in Syncable.syncablesCache)
{
syncable.DiscardSyncable();
}
}
}
internal class ObjectSync internal class ObjectSync
{ {
private static ushort _nextSyncableId = 1; private static ushort _nextSyncableId = 1;
@@ -149,8 +27,10 @@ namespace BoneSync.Sync
return _nextSyncableId++; return _nextSyncableId++;
} }
public static RegisterSyncableInfo? GetRegisterInfo(Syncable syncable)
public static RegisterSyncableInfo? GetRegisterInfo(Syncable syncable, bool allowClientRegister)
{ {
ulong ownerId = BoneSync.lobby.GetLocalId();
RegisterSyncableInfo? info = null; RegisterSyncableInfo? info = null;
string path = syncable.GetSyncableWorldPath(); string path = syncable.GetSyncableWorldPath();
if (path.Length > 0) if (path.Length > 0)
@@ -159,15 +39,26 @@ namespace BoneSync.Sync
{ {
type = RegisterSyncType.RegisterFromPath, type = RegisterSyncType.RegisterFromPath,
transformPath = path, transformPath = path,
ownerId = syncable._ownerId, ownerId = ownerId,
}; };
} }
else if (syncable.poolee) else if (syncable.poolee && syncable.poolee.pool && (BoneSync.lobby.IsHost || allowClientRegister))
{ {
string spawnableRealTitle = syncable?.poolee?.spawnObject?.title;
string spawnableAlternateTitle = syncable?.poolee?.pool?.name.Replace("pool - ", ""); // hacky, but use when real title is unknown
string spawnableTitle = spawnableRealTitle ?? spawnableAlternateTitle;
SyncLogger.Msg("Spawnable title: " + spawnableTitle);
info = new RegisterSyncableInfo() info = new RegisterSyncableInfo()
{ {
ownerId = ownerId,
type = RegisterSyncType.RegisterAndSpawn, type = RegisterSyncType.RegisterAndSpawn,
transformPath = syncable.poolee.transform.GetPath(), spawnInfo = new SpawnPoolableInfo()
{
spawnableTitle = spawnableTitle,
spawnLocation = new SimpleSyncTransform(syncable.poolee.transform, false),
},
}; };
} }
return info; return info;
@@ -175,33 +66,39 @@ namespace BoneSync.Sync
public static ushort SendRegisterSyncableMessage(Syncable syncable) public static ushort SendRegisterSyncableMessage(Syncable syncable)
{ {
RegisterSyncableInfo? infoNullable = GetRegisterInfo(syncable); SyncLogger.Msg("Sending register syncable message for: " + syncable.transform.name);
RegisterSyncableInfo? infoNullable = GetRegisterInfo(syncable, syncable.ClientSpawningAllowed());
if (infoNullable == null) if (infoNullable == null)
{ {
MelonLogger.Msg("No valid registeration method for syncable"); SyncLogger.Warning("No valid registeration method for syncable");
return 0; return 0;
} }
RegisterSyncableInfo info = infoNullable.Value; RegisterSyncableInfo info = infoNullable.Value;
RegisterSyncableMessage message = new RegisterSyncableMessage(info);
if (BoneSync.lobby.IsHost) if (BoneSync.lobby.IsHost)
{ {
info.id = GetNextId(); info.id = GetNextId();
message.Broadcast(); new RegisterSyncableMessage(info).Broadcast();
} } else
else
{ {
info.id = 0; info.callbackId = (ushort)Random.Range(1, ushort.MaxValue);
message.Send(BoneSync.lobby.GetHostId()); ObjectSyncCache.CallbackIdToSyncable[info.callbackId] = syncable;
new RegisterSyncableMessage(info).SendToHost();
} }
SyncLogger.Msg("Sending register syncable message for: " + syncable.transform.name + " with id: " + info.id + " and type : " + info.type);
return info.id; return info.id;
} }
public static void SendObjectSyncMessage(Syncable syncable) public static void SendObjectSyncMessage(Syncable syncable)
{ {
MelonLogger.Msg("Sending object sync message for: " + syncable.transform.name); if (!syncable.Registered) return;
//SyncLogger.Msg("Sending object sync message for: " + syncable.transform.name);
ObjectSyncTransform[] objectSyncTransforms = syncable.GetObjectSyncTransforms(); ObjectSyncTransform[] objectSyncTransforms = syncable.GetObjectSyncTransforms();
ObjectSyncMessageData data = new ObjectSyncMessageData() ObjectSyncMessageData data = new ObjectSyncMessageData()
@@ -213,60 +110,197 @@ namespace BoneSync.Sync
ObjectSyncMessage message = new ObjectSyncMessage(data); ObjectSyncMessage message = new ObjectSyncMessage(data);
message.Broadcast(); message.Broadcast();
} }
private static Syncable _MakeOrGetSyncable(GameObject gameObject, bool deleteSubSyncabled = true) private static Transform _GetPerferredSyncRootTransform(Transform t)
{ {
Syncable[] subSyncables = gameObject.GetComponentsInChildren<Syncable>(); if (t == null) return null;
Transform parent = t.parent;
if (parent == null) return t;
Cart cart = parent.GetComponentInParent<Cart>();
if (cart) return cart.transform;
InteractableHostManager manager = parent.GetComponentInParent<InteractableHostManager>();
if (manager) return manager.transform;
return t;
}
private static Syncable _MakeOrGetSyncable(GameObject gameObject, bool deleteSubSyncables = true)
{
//Scene scene = gameObject.scene;
//SyncLogger.Msg("Making or getting syncable for: " + gameObject.name);
if (gameObject == null)
{
return null;
}
Syncable syncable = gameObject.GetComponent<Syncable>(); Syncable syncable = gameObject.GetComponent<Syncable>();
// delete all sub syncables // delete all sub syncables
for (int i = 0; i < subSyncables.Length; i++) if (deleteSubSyncables)
{ {
if (subSyncables[i] != syncable) try
{ {
subSyncables[i].DiscardSyncable(); Syncable[] subSyncables = gameObject.GetComponentsInChildren<Syncable>(true);
for (int i = 0; i < subSyncables.Length; i++)
{
Syncable subSyncable = subSyncables[i];
if (subSyncable == syncable) continue;
if (subSyncable == null) continue;
bool isRegistered = subSyncable.Registered;
bool isPlugged = subSyncable.IsPlugged();
SyncLogger.Msg("Discarding subSyncable: " + subSyncable.transform.GetPath() + " registered:" + isRegistered + " plugged:" + isPlugged);
if (isRegistered || isPlugged) continue;
subSyncable.DiscardSyncable();
}
}
catch (Exception e)
{
SyncLogger.Warning("Failed to delete sub syncables: " + e.Message);
} }
} }
if (syncable == null) if (syncable == null)
{ {
syncable = gameObject.AddComponent<Syncable>(); Transform perferredObjectParent = _GetPerferredSyncRootTransform(gameObject.transform);
} Syncable foundComponent = perferredObjectParent.GetComponent<Syncable>();
else if (foundComponent)
{ {
syncable.FindComponents(); return foundComponent;
}
syncable = perferredObjectParent.gameObject.AddComponent<Syncable>();
} }
return syncable; return syncable;
} }
private static Syncable _GetSyncableFromCache(GameObject gameObject)
{
bool success = Syncable.syncablesCache.TryGetValue(gameObject.GetHashCode(), out Syncable syncable);
if (!success)
{
return null;
}
return syncable;
}
public static Syncable MakeOrGetSyncable(ButtonToggle buttonToggle)
{
Syncable parentSyncable = buttonToggle.GetComponentInParent<Syncable>();
if (parentSyncable)
{
return parentSyncable;
}
return MakeOrGetSyncable(buttonToggle.gameObject);
}
public static Syncable MakeOrGetSyncable(GameObject gameObject, bool deleteSubSyncables = true)
{
Syncable syncable = _GetSyncableFromCache(gameObject);
if (syncable == null)
{
syncable = _MakeOrGetSyncable(gameObject, deleteSubSyncables);
}
return syncable;
}
public static Syncable MakeOrGetSyncable(Poolee poolee)
{
return MakeOrGetSyncable(poolee.gameObject);
}
public static Syncable MakeOrGetSyncable(InteractableHost interactableHost) public static Syncable MakeOrGetSyncable(InteractableHost interactableHost)
{ {
if (interactableHost.manager) return MakeOrGetSyncable(interactableHost.manager); if (interactableHost.manager) return MakeOrGetSyncable(interactableHost.manager);
return _MakeOrGetSyncable(interactableHost.gameObject); return MakeOrGetSyncable(interactableHost.gameObject);
} }
public static Syncable MakeOrGetSyncable(InteractableHostManager interactableHostManager) public static Syncable MakeOrGetSyncable(InteractableHostManager interactableHostManager)
{ {
return _MakeOrGetSyncable(interactableHostManager.gameObject); return MakeOrGetSyncable(interactableHostManager.gameObject, true);
} }
public static Syncable SpawnPooleeAndMakeSyncable(SpawnPoolableInfo spawnInfo)
{
SimpleSyncTransform spawnLocation = spawnInfo.spawnLocation;
SpawnableObject spawnableObject = SpawnableManager.GetSpawnable(spawnInfo.spawnableTitle);
if (spawnableObject == null) {
SyncLogger.Warning("Failed to find spawnable: " + spawnInfo.spawnableTitle);
return null;
}
/*Pool pool = SpawnableManager.GetPool(spawnableObject);
if (!pool)
{
SyncLogger.Warning("[SpawnPooleeAndMakeSyncable] Failed to find pool: " + spawnInfo.spawnableTitle);
return null;
}
Poolee poolee = CallPatchedMethods.InstantiatePoolee(pool, spawnLocation.position, spawnLocation.rotation, pool.Prefab.transform.localScale);*/
Poolee poolee = SpawnableManager.SpawnPoolee(spawnableObject, spawnLocation.position, spawnLocation.rotation);
if (poolee == null) {
SyncLogger.Warning("Failed to spawn poolee: " + spawnInfo.spawnableTitle);
return null;
}
Syncable syncable = MakeOrGetSyncable(poolee);
SyncLogger.Msg("Spawned poolee with syncable: " + poolee.transform.GetPath());
return syncable;
}
public static void OnRegisterSyncMessage(RegisterSyncableMessage registerSyncableMessage) public static void OnRegisterSyncMessage(RegisterSyncableMessage registerSyncableMessage)
{ {
SyncLogger.Msg("Received register sync message");
Syncable syncable = null; Syncable syncable = null;
RegisterSyncableInfo info = registerSyncableMessage.info; RegisterSyncableInfo info = registerSyncableMessage.info;
if (BoneSync.lobby.IsHost) if (BoneSync.lobby.IsHost)
{ {
info.id = GetNextId(); info.id = GetNextId();
new RegisterSyncableMessage(info).Broadcast(); new RegisterSyncableMessage(info).Broadcast();
} else
{
// only host can register syncables, all spawn requests should be sent to host
if (registerSyncableMessage.senderId != BoneSync.lobby.GetHostId())
{
SyncLogger.Warning("Received register sync message from non-host: " + registerSyncableMessage.senderId);
return;
}
} }
switch (info.type) if (info.id == 0)
{ {
case RegisterSyncType.RegisterFromPath: SyncLogger.Warning("Received register sync message with id 0");
MelonLogger.Msg("Registering syncable from path: " + info.transformPath + " with id: " + info.id); return;
syncable = ObjectSyncCache.GetSyncable(info.transformPath); }
break;
case RegisterSyncType.RegisterAndSpawn: bool hasCallback = info.callbackId != 0;
break; if (hasCallback)
{
SyncLogger.Msg("Received register sync message with callback id: " + info.callbackId);
}
if (hasCallback && ObjectSyncCache.CallbackIdToSyncable.ContainsKey(info.callbackId))
{
SyncLogger.Msg("Found syncable for callback id: " + info.callbackId);
syncable = ObjectSyncCache.CallbackIdToSyncable[info.callbackId];
ObjectSyncCache.CallbackIdToSyncable.Remove(info.callbackId);
} else
{
switch (info.type)
{
case RegisterSyncType.RegisterFromPath:
SyncLogger.Msg("Registering syncable from path: " + info.transformPath + " with id: " + info.id);
syncable = ObjectSyncCache.GetSyncable(info.transformPath);
break;
case RegisterSyncType.RegisterAndSpawn:
SyncLogger.Msg("Registering and spawning syncable from pool: " + info.spawnInfo.Value.spawnableTitle + " with id: " + info.id);
syncable = SpawnPooleeAndMakeSyncable(info.spawnInfo.Value);
break;
}
}
if (!syncable)
{
SyncLogger.Warning("Failed to create/obtain syncable for register sync message "+ info.id);
return;
} }
syncable.SetSyncId(info.id); syncable.SetSyncId(info.id);
@@ -276,13 +310,72 @@ namespace BoneSync.Sync
public static void OnObjectSyncMessage(ObjectSyncMessage objectSyncMessage) public static void OnObjectSyncMessage(ObjectSyncMessage objectSyncMessage)
{ {
ObjectSyncMessageData data = objectSyncMessage.objectSyncMessageData; ObjectSyncMessageData data = objectSyncMessage.objectSyncMessageData;
Syncable syncable = ObjectSyncCache.GetSyncable(data.objectId); ushort objectId = data.objectId;
if (objectId == 0) return;
if (objectId >= _nextSyncableId && !BoneSync.lobby.IsHost)
{
_nextSyncableId = (ushort)(objectId + 1);
}
Syncable syncable = ObjectSyncCache.GetSyncable(objectId);
if (syncable == null) if (syncable == null)
{ {
MelonLogger.Msg("Syncable not found for id: " + data.objectId); //SyncLogger.Msg("SyncEvent: Syncable not found for id: " + objectId);
return; return;
} }
syncable.ApplyObjectSyncTransforms(data.objectSyncTransforms); syncable.ApplyObjectSyncTransforms(data.objectSyncTransforms);
} }
public static void OnObjectDamageMessage(ObjectDamageMessage damageMessge)
{
ObjectDamageInfo damageInfo = damageMessge.objectEventInfo;
if (damageInfo.objectId == 0) return;
Syncable syncable = ObjectSyncCache.GetSyncable(damageInfo.objectId);
if (syncable == null)
{
//SyncLogger.Msg("DamageEvent: Syncable not found for id: " + damageInfo.objectId);
return;
}
syncable.Damage(damageInfo);
}
public static void SendObjectDamageMessage(Syncable syncable, ObjectDamageType damageType, ObjectHealthInfo healthInfo)
{
ObjectDamageInfo damageInfo = new ObjectDamageInfo()
{
objectId = syncable.GetSyncId(),
objectHealthInfo = healthInfo,
eventType = damageType,
};
ObjectDamageMessage message = new ObjectDamageMessage(damageInfo);
message.Broadcast();
message.Execute();
}
internal static void OnOwnershipChangeMessage(ushort syncId, ulong newOwnerId, bool force)
{
Syncable syncable = ObjectSyncCache.GetSyncable(syncId);
if (syncable == null)
{
SyncLogger.Warning("Ownership transfer request for non-existant syncable: " + syncId);
return;
}
if (force)
{
syncable.SetOwner(newOwnerId);
}
else
{
syncable.OnOwnershipTransferRequest(newOwnerId);
}
}
internal static Syncable MakeOrGetSyncable(Cart instance)
{
Syncable syncable = MakeOrGetSyncable(instance.gameObject);
return syncable;
}
} }
} }

View File

@@ -0,0 +1,161 @@
using BoneSync.Sync.Components;
using StressLevelZero.Interaction;
using StressLevelZero.Pool;
using StressLevelZero.Props.Weapons;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnityEngine;
namespace BoneSync.Sync
{
public static class ObjectSyncCache
{
private static Dictionary<string, Syncable> _pathToSyncable = new Dictionary<string, Syncable>();
private static Dictionary<ushort, Syncable> _idToSyncable = new Dictionary<ushort, Syncable>();
private static Dictionary<int, Syncable> _componentToSyncable = new Dictionary<int, Syncable>();
private static Dictionary<Syncable, Component[]> _syncableToComponent = new Dictionary<Syncable, Component[]>();
public static Dictionary<ushort, Syncable> CallbackIdToSyncable = new Dictionary<ushort, Syncable>();
private static string GetComponentNamespace(Component component)
{
return component.GetType().Namespace;
}
private static bool IsCachableComponent(Component component)
{
if (component is MonoBehaviour) return true;
return false;
//string ns = GetComponentNamespace(component);
//return ns.StartsWith("StressLevelZero") || ns.StartsWith("BoneSync");
}
private static Component[] GetComponentsToCache(Syncable syncable)
{
Component[] components = syncable.GetComponentsInChildren<Component>(true);
List<Component> slzComponents = new List<Component>();
for (int i = 0; i < components.Length; i++)
{
if (IsCachableComponent(components[i]))
{
slzComponents.Add(components[i]);
}
}
return slzComponents.ToArray();
}
private static void _AddSyncableComponents(Syncable syncable)
{
Component[] components = GetComponentsToCache(syncable);
_syncableToComponent[syncable] = components;
foreach (Component component in components)
{
_componentToSyncable[component.GetHashCode()] = syncable;
}
}
private static void _RemoveSyncableComponents(Syncable syncable)
{
if (_syncableToComponent.ContainsKey(syncable))
{
Component[] components = _syncableToComponent[syncable];
foreach (Component component in components)
{
_componentToSyncable.Remove(component.GetHashCode());
}
_syncableToComponent.Remove(syncable);
}
}
private static void _AddSyncableKeys(Syncable syncable)
{
string path = syncable.GetSyncableWorldPath();
if (path != null && path != "")
{
_pathToSyncable[path] = syncable;
}
if (syncable.GetSyncId() != 0)
{
_idToSyncable[syncable.GetSyncId()] = syncable;
}
}
private static void _RemoveSyncableKeys(Syncable syncable)
{
string path = syncable.GetSyncableWorldPath();
if (path != null && path != "")
{
_pathToSyncable.Remove(path);
}
if (syncable.GetSyncId() != 0)
{
_idToSyncable.Remove(syncable.GetSyncId());
}
}
public static void AddSyncable(Syncable syncable)
{
_AddSyncableKeys(syncable);
_AddSyncableComponents(syncable);
}
public static void RemoveSyncable(Syncable syncable)
{
_RemoveSyncableKeys(syncable);
_RemoveSyncableComponents(syncable);
}
public static void UpdateSyncId(Syncable syncable)
{
// remove other enties where value is the same
ushort id = syncable.GetSyncId();
if (_idToSyncable.ContainsKey(id))
{
_idToSyncable.Remove(id);
}
_idToSyncable[id] = syncable;
}
public static Syncable GetSyncable(string path)
{
if (_pathToSyncable.ContainsKey(path))
{
return _pathToSyncable[path];
}
Transform transform = TransformExtensions.TransformFromPath(path);
if (transform)
{
return transform.GetComponent<Syncable>();
}
return null;
}
public static Syncable GetSyncable(ushort id)
{
if (_idToSyncable.ContainsKey(id))
{
return _idToSyncable[id];
}
return null;
}
public static Syncable GetSyncable(Component component)
{
if (_componentToSyncable.ContainsKey(component.GetHashCode()))
{
return _componentToSyncable[component.GetHashCode()];
}
return null;
}
public static void DISCARD_ALL_SYNCABLES()
{
foreach (Syncable syncable in Syncable.syncablesCache.Values)
{
syncable.DiscardSyncableImmediate(true, false);
}
}
}
}

View File

@@ -1,57 +0,0 @@
using BoneSync.Networking;
using BoneSync.Networking.Messages;
using BoneSync.Player;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace BoneSync.Sync
{
internal static class PlayerSync
{
private static GameObject _localPlayerRig;
private static Dictionary<ulong, PlayerRig> _playerRigMap = new Dictionary<ulong, PlayerRig>();
private static PlayerRig GetPlayerRig(ulong playerId)
{
if (!_playerRigMap.ContainsKey(playerId))
{
PlayerRig playerRig = new PlayerRig();
_playerRigMap.Add(playerId, playerRig);
}
return _playerRigMap[playerId];
}
public static void CleanUp()
{
foreach (PlayerRig playerRig in _playerRigMap.Values)
{
playerRig.Destroy();
}
_playerRigMap.Clear();
}
public static void SyncPlayer()
{
PlayerSyncInfo playerSyncInfo = new PlayerSyncInfo();
playerSyncInfo.headPos = new SimpleTransform();
playerSyncInfo.leftHandPos = new SimpleTransform();
playerSyncInfo.rightHandPos = new SimpleTransform();
PlayerSyncMessage playerSyncMessage = new PlayerSyncMessage(playerSyncInfo);
playerSyncMessage.Broadcast();
}
public static void OnPlayerSync(PlayerSyncMessage playerSyncMessage)
{
PlayerRig playerRig = GetPlayerRig(playerSyncMessage.senderId);
playerRig.UpdatePlayerSync(playerSyncMessage.playerSyncInfo);
}
}
}

View File

@@ -1,17 +1,28 @@
using MelonLoader; using BoneSync.Data;
using BoneSync.Networking.Messages;
using BoneSync.Sync.Components;
using MelonLoader;
using StressLevelZero.Interaction;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
namespace BoneSync.Sync namespace BoneSync.Sync
{ {
internal class SceneSync internal class SceneSync
{ {
public const float MAP_LOAD_GRACE_PERIOD = 5f; // allow x seconds for the game to load the scene before doing anything that requires the scene to be fully initialized
private static List<Scene> scenes = new List<Scene>(); private static List<Scene> scenes = new List<Scene>();
private static string _currentSceneName; private static string _currentSceneName;
private static int _currentSceneIndex;
private static float _lastSceneChangeTime;
public static float TimeSinceLastSceneChange => Time.realtimeSinceStartup - _lastSceneChangeTime;
public static int CurrentSceneIndex => _currentSceneIndex;
public static string CurrentSceneDisplayName public static string CurrentSceneDisplayName
{ {
@@ -28,11 +39,44 @@ namespace BoneSync.Sync
return sceneName; return sceneName;
} }
} }
public static void RenameDuplicateSceneTransforms(Scene scene)
{
Dictionary<string, ushort> seenTransformNames = new Dictionary<string, ushort>();
uint total = 0;
foreach (GameObject go in scene.GetRootGameObjects())
{
foreach (InteractableHost host in go.GetComponentsInChildren<InteractableHost>(true))
{
Transform t = host.transform;
string path = t.GetPath();
if (seenTransformNames.ContainsKey(path))
{
seenTransformNames[path]++;
t.name = t.name + " (BoneSync." + seenTransformNames[path] + ")";
total++;
//SyncLogger.Msg("Renamed duplicate transform: " + path);
}
else
{
seenTransformNames[path] = 1;
}
}
}
SyncLogger.Msg("Renamed " + total + " duplicate transforms in " + scene.name);
}
public static void OnSceneInit(int buildIndex) public static void OnSceneInit(int buildIndex)
{ {
string SceneName = SceneManager.GetSceneByBuildIndex(buildIndex).name; Scene scene = SceneManager.GetSceneByBuildIndex(buildIndex);
_lastSceneChangeTime = Time.realtimeSinceStartup;
_currentSceneIndex = buildIndex;
string SceneName = scene.name;
_currentSceneName = SceneName; _currentSceneName = SceneName;
MelonLogger.Msg("Scene initialized: " + SceneName); SyncLogger.Msg("Scene initialized: " + SceneName);
RenameDuplicateSceneTransforms(scene);
SpawnableManager.AddUnregisteredSpawnables();
SendSceneSyncMessage(buildIndex);
} }
public static void Initialize() public static void Initialize()
@@ -42,5 +86,32 @@ namespace BoneSync.Sync
scenes.Add(SceneManager.GetSceneByBuildIndex(i)); scenes.Add(SceneManager.GetSceneByBuildIndex(i));
} }
} }
public static void SendSceneSyncMessage(string sceneName)
{
int index = scenes.FindIndex(x => x.name == sceneName);
if (index == -1)
{
SyncLogger.Error("Scene not found: " + sceneName);
return;
}
SendSceneSyncMessage(index);
}
public static void SendSceneSyncMessage(int index)
{
if (!BoneSync.IsConnected) return;
if (BoneSync.lobby.IsHost)
{
SyncLogger.Msg("Host is loading scene, sending message to clients...");
SceneChangeInfo info = new SceneChangeInfo()
{
sceneName = scenes[index].name,
sceneChangeType = sceneChangeType.FromIndex,
sceneIndex = (byte)index
};
SceneChangeMessage message = new SceneChangeMessage(info);
message.Broadcast();
}
}
} }
} }

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.TestPlatform.ObjectModel" version="17.12.0" targetFramework="net472" />
<package id="MSTest.Analyzers" version="3.8.2" targetFramework="net472" developmentDependency="true" />
<package id="MSTest.TestFramework" version="3.8.2" targetFramework="net472" />
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net472" />
<package id="System.Reflection.Metadata" version="1.6.0" targetFramework="net472" />
<package id="xunit" version="2.9.3" targetFramework="net472" />
<package id="xunit.abstractions" version="2.0.3" targetFramework="net472" />
<package id="xunit.analyzers" version="1.20.0" targetFramework="net472" developmentDependency="true" />
<package id="xunit.assert" version="2.9.3" targetFramework="net472" />
<package id="xunit.core" version="2.9.3" targetFramework="net472" />
<package id="xunit.extensibility.core" version="2.9.3" targetFramework="net472" />
<package id="xunit.extensibility.execution" version="2.9.3" targetFramework="net472" />
<package id="xunit.runner.visualstudio" version="3.0.2" targetFramework="net472" developmentDependency="true" />
</packages>

BIN
BoneSync/playerrep.eres Normal file

Binary file not shown.