diff --git a/.github/workflows/SIT-CD.yml b/.github/workflows/SIT-CD.yml index 81f9ded0d..d4d0bb795 100644 --- a/.github/workflows/SIT-CD.yml +++ b/.github/workflows/SIT-CD.yml @@ -3,7 +3,6 @@ name: SIT Build Release on: workflow_dispatch: - permissions: contents: write @@ -16,78 +15,48 @@ jobs: matrix: configuration: [Release] - runs-on: windows-latest + runs-on: ubuntu-latest env: SolutionName: StayInTarkov - CSProj: Source/StayInTarkov.csproj + ProjectClient: Source/StayInTarkov.csproj + ProjectPrePatcher: SIT.WildSpawnType.PrePatcher/SIT.WildSpawnType.PrePatcher.csproj steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - - # Install the .NET Core workload - name: Install .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 6.0.x - # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@v1.0.2 - - # Restore all projects - name: dotnet Restore run: dotnet restore - # Publish the Launcher as Self-Contained Single-File - - name: dotnet Publish + - name: dotnet build run: | mkdir ${{ env.SolutionName }}-${{ matrix.configuration }} - dotnet build ${{ env.CSProj }} -c ${{ matrix.configuration }} -o ${{ env.SolutionName }}-${{ matrix.configuration }} + dotnet build ${{ env.ProjectClient }} -c ${{ matrix.configuration }} -o ${{ env.SolutionName }}-${{ matrix.configuration }} + dotnet build ${{ env.ProjectPrePatcher }} -c ${{ matrix.configuration }} -o ${{ env.SolutionName }}-${{ matrix.configuration }} - name: Get version from DLL id: extract-version run: | - $version = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("${{ env.SolutionName }}-${{ matrix.configuration }}\StayInTarkov.dll").ProductVersion - echo "::set-output name=VERSION::$version" - - - - name: Remove unnecessary files - run: | - del ${{ env.SolutionName }}-${{ matrix.configuration }}\StayInTarkov.pdb - - #- name: Display Version in Logs - # run: echo "The extracted version is ${{ steps.extract-version.outputs.VERSION }}" + version=`monodis --assembly ${{ env.SolutionName }}-${{ matrix.configuration }}/StayInTarkov.dll | grep Version | awk '{print $2}'` + echo "VERSION=$version" >> "$GITHUB_ENV" - name: Zip remaining files run: | - Compress-Archive -Path ${{ env.SolutionName }}-${{ matrix.configuration }} -DestinationPath ${{ env.SolutionName }}-${{ matrix.configuration }}.zip - + cd ${{ env.SolutionName }}-${{ matrix.configuration }} + zip -r ../${{ env.SolutionName }}-${{ matrix.configuration }}.zip ./*.dll - - # Upload artifact unless its merge to master - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: StayInTarkov-${{ matrix.configuration }} - path: ${{ env.SolutionName }}-${{ matrix.configuration }}\ - if-no-files-found: error - - #- name: Set build date - # run: | - # $NOW=& Get-Date -format yyyy-MM-dd-HH-mm - # echo "NOW=$NOW" >> $env:GITHUB_ENV - - # Create release as draft from the compressed file - name: Create Release uses: softprops/action-gh-release@v1 - if: ${{ matrix.configuration == 'Release' }} with: draft: true generate_release_notes: true files: ${{ env.SolutionName }}-${{ matrix.configuration }}.zip - tag_name: StayInTarkov.Client-${{ steps.extract-version.outputs.VERSION }} + tag_name: StayInTarkov.Client-${{ env.VERSION }} diff --git a/.github/workflows/SIT-CI-Linux.yml b/.github/workflows/SIT-CI-Linux.yml index 7d69554f2..1a5467d37 100644 --- a/.github/workflows/SIT-CI-Linux.yml +++ b/.github/workflows/SIT-CI-Linux.yml @@ -28,16 +28,17 @@ jobs: env: SolutionName: StayInTarkov - CSProj: Source/StayInTarkov.csproj + ProjectClient: Source/StayInTarkov.csproj + ProjectPrePatcher: SIT.WildSpawnType.PrePatcher/SIT.WildSpawnType.PrePatcher.csproj steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 6.0.x @@ -49,12 +50,13 @@ jobs: - name: dotnet Publish run: | mkdir ${{ env.SolutionName }}-${{ matrix.configuration }} - dotnet build ${{ env.CSProj }} -c ${{ matrix.configuration }} -o ${{ env.SolutionName }}-${{ matrix.configuration }} + dotnet build ${{ env.ProjectClient }} -c ${{ matrix.configuration }} -o ${{ env.SolutionName }}-${{ matrix.configuration }} + dotnet build ${{ env.ProjectPrePatcher }} -c ${{ matrix.configuration }} -o ${{ env.SolutionName }}-${{ matrix.configuration }} # Remove unnecessary files - name: Remove unnecessary files run: | - rm ${{ env.SolutionName }}-${{ matrix.configuration }}/StayInTarkov.pdb + rm ${{ env.SolutionName }}-${{ matrix.configuration }}/*.pdb # Zip remaining files - name: Zip remaining files @@ -63,7 +65,7 @@ jobs: # Upload artifact with GitHub commit SHA - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.SolutionName }}-${{ matrix.configuration }}-${{ github.sha }} path: ${{ env.SolutionName }}-${{ matrix.configuration }}/ diff --git a/.github/workflows/SIT-CI.yml b/.github/workflows/SIT-CI.yml deleted file mode 100644 index 1069d88ad..000000000 --- a/.github/workflows/SIT-CI.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: SIT CI Windows - -on: - #pull_request: - #types: - #- opened - #- synchronize - #- reopened - #paths-ignore: - #- '.github/**' - #- '*.md' - #branches: - #- "master" - - workflow_dispatch: - - -jobs: - Build-SIT: - - strategy: - matrix: - configuration: [Debug] - - - runs-on: windows-latest - - env: - SolutionName: StayInTarkov - CSProj: Source/StayInTarkov.csproj - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 -# Install the .NET Core workload - - name: Install .NET Core - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 6.0.x - - # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@v1.0.2 - - - # Restore all projects - - name: dotnet Restore - run: dotnet restore - - # Build the project - - name: dotnet Publish - run: | - mkdir ${{ env.SolutionName }}-${{ matrix.configuration }} - dotnet build ${{ env.CSProj }} -c ${{ matrix.configuration }} -o ${{ env.SolutionName }}-${{ matrix.configuration }} - # Remove unnecessary files - - name: Remove unnecessary files - run: | - del ${{ env.SolutionName }}-${{ matrix.configuration }}\StayInTarkov.pdb - # Zip remaining files - - name: Zip remaining files - run: | - Compress-Archive -Path ${{ env.SolutionName }}-${{ matrix.configuration }} -DestinationPath ${{ env.SolutionName }}-${{ matrix.configuration }}.zip - # Upload artifact with github commit SHA - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: ${{ env.SolutionName }}-${{ matrix.configuration }}-${{ github.sha }} - path: ${{ env.SolutionName }}-${{ matrix.configuration }}\ - if-no-files-found: error diff --git a/.github/workflows/publish-wiki.yml b/.github/workflows/publish-wiki.yml index 0a15b73f2..35dba07ef 100644 --- a/.github/workflows/publish-wiki.yml +++ b/.github/workflows/publish-wiki.yml @@ -15,5 +15,5 @@ jobs: publish-wiki: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Andrew-Chen-Wang/github-wiki-action@v4 diff --git a/.gitignore b/.gitignore index 2fef73d69..3467fe93d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ # [Bb]in/ [Dd]ebug/ +/vcpkg_installed # User-specific files *.rsuser diff --git a/COMPILE.md b/COMPILE.md index f0aef9c83..38abb7578 100644 --- a/COMPILE.md +++ b/COMPILE.md @@ -1,15 +1,22 @@ -## How to compile? +## How to compile Master branch? +1. Create Working Directory for all Tarkov Modding {EFT_WORK} +2. Clone this {SIT_CORE} to a {SIT_CORE} directory inside {EFT_WORK} +3. Open the .sln with Visual Studio 2022 +4. Rebuild Solution (This should download and install all nuget packages on compilation) + +## How to compile on Latest EFT? 1. Create Working Directory for all Tarkov Modding {EFT_WORK} 2. Clone this {SIT_CORE} to a {SIT_CORE} directory inside {EFT_WORK} 3. Copy your Live Tarkov Directory somewhere else {EFT_OFFLINE} -4. Deobfuscate latest Assembly-CSharp in {EFT_OFFLINE} via [SIT.Launcher](https://github.com/paulov-t/SIT.Tarkov.Launcher). Ensure to close and restart Launcher after Deobfuscation. -5. Copy all of {EFT_OFFLINE}\EscapeFromTarkov_Data\Managed assemblies to References {TARKOV.REF} in the folder of this project {EFT_WORK} +4. Deobfuscate latest Assembly-CSharp in {EFT_OFFLINE} via [SIT.Launcher](https://github.com/paulov-t/SIT.Launcher.Classic). Ensure to close and restart Launcher after Deobfuscation. +5. Copy {EFT_OFFLINE}\EscapeFromTarkov_Data\Managed\Assembly-CSharp to References {TARKOV.REF} in the folder of this project {EFT_WORK} 6. You will need BepInEx Nuget Feed installed on your PC by running the following command in a terminal. ``` dotnet new -i BepInEx.Templates --nuget-source https://nuget.bepinex.dev/v3/index.json ``` 7. Open the .sln with Visual Studio 2022 -8. Rebuild Solution (This should download and install all nuget packages on compilation) +8. Copy the `contents` of `_GlobalUsings.SITRemapperConfig.cs` into `GlobalUsings.cs` in this project. `!Manually remove bad remaps!` +9. Rebuild Solution (This should download and install all nuget packages on compilation) -## Which version of BepInEx is this built for? +## Which version of BepInEx is this project compatible with? Version 5 \ No newline at end of file diff --git a/References/Assembly-CSharp.dll b/References/Assembly-CSharp.dll index 45f31f5b3..7e9b21dcd 100644 Binary files a/References/Assembly-CSharp.dll and b/References/Assembly-CSharp.dll differ diff --git a/SIT.WildSpawnType.PrePatcher/SIT.WildSpawnType.PrePatcher.csproj b/SIT.WildSpawnType.PrePatcher/SIT.WildSpawnType.PrePatcher.csproj new file mode 100644 index 000000000..0f4f01015 --- /dev/null +++ b/SIT.WildSpawnType.PrePatcher/SIT.WildSpawnType.PrePatcher.csproj @@ -0,0 +1,28 @@ + + + + + net471 + SIT.WildSpawnType.PrePatcher + Release + + + + Stay In Tarkov + Copyright @ Stay In Tarkov 2024 + Stay in Tarkov plugin for Escape From Tarkov + + + + + + + + + + compile; build; native; contentfiles; analyzers; buildtransitive + + + + + \ No newline at end of file diff --git a/SIT.WildSpawnType.PrePatcher/WildSpawnTypePrePatcher.cs b/SIT.WildSpawnType.PrePatcher/WildSpawnTypePrePatcher.cs new file mode 100644 index 000000000..5ee24a04b --- /dev/null +++ b/SIT.WildSpawnType.PrePatcher/WildSpawnTypePrePatcher.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil; + +namespace StayInTarkov +{ + public static class WildSpawnTypePrePatcher + { + public static IEnumerable TargetDLLs { get; } = new[] { "Assembly-CSharp.dll" }; + + public static int sptUsecValue = 47; + public static int sptBearValue = 48; + + public static void Patch(ref AssemblyDefinition assembly) + { + var botEnums = assembly.MainModule.GetType("EFT.WildSpawnType"); + + var sptUsec = new FieldDefinition("sptUsec", + FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault, + botEnums) + { Constant = sptUsecValue }; + + var sptBear = new FieldDefinition("sptBear", + FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault, + botEnums) + { Constant = sptBearValue }; + + if(!botEnums.Fields.Any(x => x.Name == "sptUsec")) + botEnums.Fields.Add(sptUsec); + + if(!botEnums.Fields.Any(x => x.Name == "sptBear")) + botEnums.Fields.Add(sptBear); + } + } +} \ No newline at end of file diff --git a/Source/AI/BlockerErrorFixPatch.cs b/Source/AI/BlockerErrorFixPatch.cs new file mode 100644 index 000000000..8ded2178c --- /dev/null +++ b/Source/AI/BlockerErrorFixPatch.cs @@ -0,0 +1,34 @@ +//using EFT; +//using HarmonyLib; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Reflection; +//using System.Security.Cryptography.X509Certificates; +//using System.Text; +//using System.Threading.Tasks; + +//namespace StayInTarkov.AI +//{ +// /// +// /// Paulov: Stay in Tarkov causes an error with the phrase BLOCKER ERROR caused by spt WildSpawnTypes +// /// This patch resolves this error by adding the spt WildSpawnTypes to the ServerBotSettingsClass +// /// LICENSE: This patch is only for use in Stay In Tarkov +// /// +// public sealed class BlockerErrorFixPatch : ModulePatch +// { +// protected override MethodBase GetTargetMethod() +// { +// return AccessTools.GetDeclaredMethods(typeof(ServerBotSettingsClass)).First(x => x.Name == nameof(ServerBotSettingsClass.Init)); +// } + +// [PatchPrefix] +// public static bool Prefix(Dictionary ___dictionary_0) +// { +// var enumValues = Enum.GetValues(typeof(WildSpawnType)); +// ___dictionary_0.Add((WildSpawnType)enumValues.GetValue(enumValues.Length - 2), new ServerBotSettingsValuesClass(false, false, true, "ScavRole/PmcBot")); +// ___dictionary_0.Add((WildSpawnType)enumValues.GetValue(enumValues.Length - 1), new ServerBotSettingsValuesClass(false, false, true, "ScavRole/PmcBot")); +// return true; +// } +// } +//} diff --git a/Source/AkiSupport/Airdrops/AirdropBox.cs b/Source/AkiSupport/Airdrops/AirdropBox.cs index 51f56f61c..b39e3b5ad 100644 --- a/Source/AkiSupport/Airdrops/AirdropBox.cs +++ b/Source/AkiSupport/Airdrops/AirdropBox.cs @@ -2,16 +2,18 @@ using EFT.Airdrop; using EFT.Interactive; using EFT.SynchronizableObjects; +using StayInTarkov.Coop.Matchmaker; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; +using UnityEngine.AI; namespace StayInTarkov.AkiSupport.Airdrops { /// /// Created by: SPT-Aki team - /// Link: https://dev.sp-tarkov.com/SPT-AKI/Modules/src/branch/master/project/Aki.Custom/Airdrops/AirdropBox.cs + /// Link: https://dev.sp-tarkov.com/SPT/Modules/src/branch/master/project/SPT.Custom/Airdrops/AirdropBox.cs /// public class AirdropBox : MonoBehaviour { @@ -20,6 +22,8 @@ public class AirdropBox : MonoBehaviour private readonly int CROSSFADE = Shader.PropertyToID("_Crossfade"); private readonly int COLLISION = Animator.StringToHash("collision"); + private static int AirdropContainerCount = 0; + public LootableContainer Container { get; set; } private float fallSpeed; private AirdropSynchronizableObject boxSync; @@ -44,12 +48,16 @@ private BetterSource AudioSource } } + public Vector3 ClientSyncPosition { get; internal set; } + public static async Task Init(float crateFallSpeed) { var instance = (await LoadCrate()).AddComponent(); instance.soundsDictionary = await LoadSounds(); instance.Container = instance.GetComponentInChildren(); + instance.Container.Id = $"AirdropBox_{AirdropContainerCount}"; + AirdropContainerCount++; instance.boxSync = instance.GetComponent(); instance.boxLogic = new AirdropLogicClass(); @@ -121,6 +129,19 @@ public IEnumerator DropCrate(Vector3 position) OpenParachute(); while (RaycastBoxDistance(LayerMaskClass.TerrainLowPoly, out _)) { + if (SITMatchmaking.IsClient) + { + + if (transform.position.x != this.ClientSyncPosition.x) + transform.position = ClientSyncPosition; + + // only do this if the box is higher than the client sync + if (transform.position.y > this.ClientSyncPosition.y) + transform.position = ClientSyncPosition; + + if (transform.position.z != this.ClientSyncPosition.z) + transform.position = ClientSyncPosition; + } transform.Translate(Vector3.down * (Time.deltaTime * fallSpeed)); transform.Rotate(Vector3.up, Time.deltaTime * 6f); yield return null; @@ -147,6 +168,15 @@ private void OnBoxLand(out float clipLength) Falloff = (int)surfaceSet.LandingSoundBank.Rolloff, Volume = surfaceSet.LandingSoundBank.BaseVolume }); + + AddNavMeshObstacle(); + } + + private void AddNavMeshObstacle() + { + var navMeshObstacle = this.GetOrAddComponent(); + navMeshObstacle.size = boxSync.CollisionCollider.bounds.size; + navMeshObstacle.carving = true; } private bool RaycastBoxDistance(LayerMask layerMask, out RaycastHit hitInfo) diff --git a/Source/AkiSupport/Airdrops/Patches/AirdropFlarePatch.cs b/Source/AkiSupport/Airdrops/Patches/AirdropFlarePatch.cs index e1c63b1b7..b0bed908d 100644 --- a/Source/AkiSupport/Airdrops/Patches/AirdropFlarePatch.cs +++ b/Source/AkiSupport/Airdrops/Patches/AirdropFlarePatch.cs @@ -20,18 +20,19 @@ public class AirdropFlarePatch : ModulePatch protected override MethodBase GetTargetMethod() { - return ReflectionHelpers.GetMethodForType(typeof(FlareCartridge), nameof(FlareCartridge.Init), false, true); + return typeof(FlareCartridge).GetMethod(nameof(FlareCartridge.Init), + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); } [PatchPostfix] - private static void PatchPostfix(FlareCartridgeSettings flareCartridgeSettings, IPlayer player, BulletClass flareCartridge, Weapon weapon) + private static void PatchPostfix(BulletClass flareCartridge) { var gameWorld = Singleton.Instance; var points = LocationScene.GetAll().Any(); if (gameWorld != null && points && _usableFlares.Any(x => x == flareCartridge.Template._id)) { - gameWorld.gameObject.AddComponent().isFlareDrop = true; + gameWorld.gameObject.AddComponent().IsFlareDrop = true; } } } diff --git a/Source/AkiSupport/Airdrops/Utils/AirdropUtil.cs b/Source/AkiSupport/Airdrops/Utils/AirdropUtil.cs index 2008566ab..dccb2d2d4 100644 --- a/Source/AkiSupport/Airdrops/Utils/AirdropUtil.cs +++ b/Source/AkiSupport/Airdrops/Utils/AirdropUtil.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using UnityEngine; using Random = UnityEngine.Random; @@ -15,9 +16,9 @@ namespace StayInTarkov.AkiSupport.Airdrops.Utils { public static class AirdropUtil { - public static AirdropConfigModel GetConfigFromServer() + public static async Task GetConfigFromServer() { - string json = AkiBackendCommunication.Instance.GetJson("/singleplayer/airdrop/config"); + string json = await AkiBackendCommunication.Instance.GetJsonAsync("/singleplayer/airdrop/config"); return JsonConvert.DeserializeObject(json); } @@ -79,9 +80,9 @@ public static bool ShouldAirdropOccur(int dropChance, List airdrop return airdropPoints.Count > 0 && Random.Range(0, 100) <= dropChance; } - public static AirdropParametersModel InitAirdropParams(GameWorld gameWorld, bool isFlare) + public static async Task InitAirdropParams(GameWorld gameWorld, bool isFlare) { - var serverConfig = GetConfigFromServer(); + var serverConfig = await GetConfigFromServer(); if (serverConfig == null) return new AirdropParametersModel() { Config = serverConfig, AirdropAvailable = false }; diff --git a/Source/AkiSupport/Airdrops/Utils/ItemFactoryUtil.cs b/Source/AkiSupport/Airdrops/Utils/ItemFactoryUtil.cs index b7992bb8d..36d85e6e2 100644 --- a/Source/AkiSupport/Airdrops/Utils/ItemFactoryUtil.cs +++ b/Source/AkiSupport/Airdrops/Utils/ItemFactoryUtil.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using StayInTarkov.Networking; using System.Linq; +using System.Threading.Tasks; using UnityEngine; namespace Aki.Custom.Airdrops.Utils @@ -60,9 +61,9 @@ public async void AddLoot(LootableContainer container, AirdropLootResultModel lo } } - public AirdropLootResultModel GetLoot() + public async Task GetLoot() { - var json = AkiBackendCommunication.Instance.GetJson("/client/location/getAirdropLoot"); + var json = await AkiBackendCommunication.Instance.GetJsonAsync("/client/location/getAirdropLoot"); var result = JsonConvert.DeserializeObject(json); return result; diff --git a/Source/AkiSupport/Custom/BotDifficultyPatch.cs b/Source/AkiSupport/Custom/BotDifficultyPatch.cs index 99593118c..2b28cb82f 100644 --- a/Source/AkiSupport/Custom/BotDifficultyPatch.cs +++ b/Source/AkiSupport/Custom/BotDifficultyPatch.cs @@ -2,6 +2,7 @@ using StayInTarkov.Networking; using System.Linq; using System.Reflection; +using System.Threading.Tasks; namespace StayInTarkov.AkiSupport.Custom { @@ -23,7 +24,7 @@ protected override MethodBase GetTargetMethod() [PatchPrefix] private static bool PatchPrefix(ref string __result, BotDifficulty botDifficulty, WildSpawnType role) { - __result = AkiBackendCommunication.Instance.GetJson($"/singleplayer/settings/bot/difficulty/{role}/{botDifficulty}"); + __result = AkiBackendCommunication.Instance.GetJsonBLOCKING($"/singleplayer/settings/bot/difficulty/{role}/{botDifficulty}"); return string.IsNullOrWhiteSpace(__result); } } diff --git a/Source/AkiSupport/Custom/CoreDifficultyPatch.cs b/Source/AkiSupport/Custom/CoreDifficultyPatch.cs index 19a1721e7..2783fccde 100644 --- a/Source/AkiSupport/Custom/CoreDifficultyPatch.cs +++ b/Source/AkiSupport/Custom/CoreDifficultyPatch.cs @@ -22,7 +22,7 @@ protected override MethodBase GetTargetMethod() [PatchPrefix] private static bool PatchPrefix(ref string __result) { - __result = AkiBackendCommunication.Instance.GetJson("/singleplayer/settings/bot/difficulty/core/core"); + __result = AkiBackendCommunication.Instance.GetJsonBLOCKING("/singleplayer/settings/bot/difficulty/core/core"); return string.IsNullOrWhiteSpace(__result); } } diff --git a/Source/AkiSupport/Custom/CustomAI/AIBrainSpawnWeightAdjustment.cs b/Source/AkiSupport/Custom/CustomAI/AIBrainSpawnWeightAdjustment.cs new file mode 100644 index 000000000..1f82e4f63 --- /dev/null +++ b/Source/AkiSupport/Custom/CustomAI/AIBrainSpawnWeightAdjustment.cs @@ -0,0 +1,176 @@ +using BepInEx.Logging; +using EFT; +using Newtonsoft.Json; +using StayInTarkov.Networking; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace StayInTarkov.AkiSupport.Custom.CustomAI +{ + /// + /// Created by: SPT-Aki + /// Link: https://dev.sp-tarkov.com/SPT/Modules/src/branch/master/project/SPT.Custom/CustomAI/AIBrainSpawnWeightAdjustment.cs + /// + public class AIBrainSpawnWeightAdjustment + { + private static AIBrains aiBrainsCache = null; + private static DateTime aiBrainCacheDate = new DateTime(); + private static readonly Random random = new Random(); + private readonly ManualLogSource logger; + + public AIBrainSpawnWeightAdjustment(ManualLogSource logger) + { + this.logger = logger; + } + + public WildSpawnType GetRandomisedPlayerScavType(BotOwner botOwner, string currentMapName) + { + // Get map brain weights from server and cache + if (aiBrainsCache == null || CacheIsStale()) + { + ResetCacheDate(); + HydrateCacheWithServerData(); + + if (!aiBrainsCache.playerScav.TryGetValue(currentMapName.ToLower(), out _)) + { + throw new Exception($"Bots were refreshed from the server but the assault cache still doesnt contain data"); + } + } + + // Choose random weighted brain + var randomType = WeightedRandom(aiBrainsCache.playerScav[currentMapName.ToLower()].Keys.ToArray(), aiBrainsCache.playerScav[currentMapName.ToLower()].Values.ToArray()); + if (Enum.TryParse(randomType, out WildSpawnType newAiType)) + { + logger.LogWarning($"Updated player scav bot to use: {newAiType} brain"); + return newAiType; + } + else + { + logger.LogWarning($"Updated player scav bot {botOwner.Profile.Info.Nickname}: {botOwner.Profile.Info.Settings.Role} to use: {newAiType} brain"); + + return newAiType; + } + } + + public WildSpawnType GetAssaultScavWildSpawnType(BotOwner botOwner, string currentMapName) + { + // Get map brain weights from server and cache + if (aiBrainsCache == null || CacheIsStale()) + { + ResetCacheDate(); + HydrateCacheWithServerData(); + + if (!aiBrainsCache.assault.TryGetValue(currentMapName.ToLower(), out _)) + { + throw new Exception($"Bots were refreshed from the server but the assault cache still doesnt contain data"); + } + } + + // Choose random weighted brain + var randomType = WeightedRandom(aiBrainsCache.assault[currentMapName.ToLower()].Keys.ToArray(), aiBrainsCache.assault[currentMapName.ToLower()].Values.ToArray()); + if (Enum.TryParse(randomType, out WildSpawnType newAiType)) + { + logger.LogWarning($"Updated assault bot to use: {newAiType} brain"); + return newAiType; + } + else + { + logger.LogWarning($"Updated assault bot {botOwner.Profile.Info.Nickname}: {botOwner.Profile.Info.Settings.Role} to use: {newAiType} brain"); + + return newAiType; + } + } + + public WildSpawnType GetPmcWildSpawnType(BotOwner botOwner_0, WildSpawnType pmcType, string currentMapName) + { + if (aiBrainsCache == null || !aiBrainsCache.pmc.TryGetValue(pmcType, out var botSettings) || CacheIsStale()) + { + ResetCacheDate(); + HydrateCacheWithServerData(); + + if (!aiBrainsCache.pmc.TryGetValue(pmcType, out botSettings)) + { + throw new Exception($"Bots were refreshed from the server but the cache still doesnt contain an appropriate bot for type {botOwner_0.Profile.Info.Settings.Role}"); + } + } + + var mapSettings = botSettings[currentMapName.ToLower()]; + var randomType = WeightedRandom(mapSettings.Keys.ToArray(), mapSettings.Values.ToArray()); + if (Enum.TryParse(randomType, out WildSpawnType newAiType)) + { + logger.LogWarning($"Updated spt bot {botOwner_0.Profile.Info.Nickname}: {botOwner_0.Profile.Info.Settings.Role} to use: {newAiType} brain"); + + return newAiType; + } + else + { + logger.LogError($"Couldnt not update spt bot {botOwner_0.Profile.Info.Nickname} to random type {randomType}, does not exist for WildSpawnType enum, defaulting to 'assault'"); + + return WildSpawnType.assault; + } + } + + private void HydrateCacheWithServerData() + { + // Get weightings for PMCs from server and store in dict + var result = AkiBackendCommunication.Instance.GetJsonBLOCKING($"/singleplayer/settings/bot/getBotBehaviours/"); + aiBrainsCache = JsonConvert.DeserializeObject(result); + logger.LogWarning($"Cached ai brain weights in client"); + } + + private void ResetCacheDate() + { + aiBrainCacheDate = DateTime.Now; + aiBrainsCache?.pmc?.Clear(); + aiBrainsCache?.assault?.Clear(); + aiBrainsCache?.playerScav?.Clear(); + } + + private static bool CacheIsStale() + { + TimeSpan cacheAge = DateTime.Now - aiBrainCacheDate; + + return cacheAge.Minutes > 15; + } + + public class AIBrains + { + public Dictionary>> pmc { get; set; } + public Dictionary> assault { get; set; } + public Dictionary> playerScav { get; set; } + } + + /// + /// Choose a value from a choice of values with weightings + /// + /// + /// + /// + private string WeightedRandom(string[] botTypes, int[] weights) + { + var cumulativeWeights = new int[botTypes.Length]; + + for (int i = 0; i < weights.Length; i++) + { + cumulativeWeights[i] = weights[i] + (i == 0 ? 0 : cumulativeWeights[i - 1]); + } + + var maxCumulativeWeight = cumulativeWeights[cumulativeWeights.Length - 1]; + var randomNumber = maxCumulativeWeight * random.NextDouble(); + + for (var itemIndex = 0; itemIndex < botTypes.Length; itemIndex++) + { + if (cumulativeWeights[itemIndex] >= randomNumber) + { + return botTypes[itemIndex]; + } + } + + logger.LogError("failed to get random bot weighting, returned assault"); + + return "assault"; + } + } + +} diff --git a/Source/AkiSupport/Custom/CustomAI/AiHelpers.cs b/Source/AkiSupport/Custom/CustomAI/AiHelpers.cs new file mode 100644 index 000000000..cabd0263c --- /dev/null +++ b/Source/AkiSupport/Custom/CustomAI/AiHelpers.cs @@ -0,0 +1,56 @@ +using EFT; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StayInTarkov.AkiSupport.Custom.CustomAI +{ + /// + /// Created by: SPT-Aki + /// Link: https://dev.sp-tarkov.com/SPT/Modules/src/branch/master/project/SPT.Custom/CustomAI/AiHelpers.cs + /// + public static class AiHelpers + { + /// + /// Bot is a PMC when it has IsStreamerModeAvailable flagged and has a wildspawn type of 'sptBear' or 'sptUsec' + /// + /// Bots role + /// Bot details + /// + public static bool BotIsSptPmc(WildSpawnType botRoleToCheck, BotOwner ___botOwner_0) + { + if (___botOwner_0.Profile.Info.IsStreamerModeAvailable) + { + // PMCs can sometimes have thier role changed to 'assaultGroup' by the client, we need a alternate way to figure out if they're a spt pmc + return true; + } + + return (int) botRoleToCheck == WildSpawnTypePrePatcher.sptBearValue || (int) botRoleToCheck == WildSpawnTypePrePatcher.sptUsecValue; + } + + public static bool BotIsPlayerScav(WildSpawnType role, string nickname) + { + if (role == WildSpawnType.assault && nickname.Contains("(")) + { + // Check bot is pscav by looking for the opening parentheses of their nickname e.g. scavname (pmc name) + return true; + } + + return false; + } + + public static bool BotIsNormalAssaultScav(WildSpawnType role, BotOwner ___botOwner_0) + { + // Is assault + no ( + if (!___botOwner_0.Profile.Info.Nickname.Contains("(") && role == WildSpawnType.assault) + { + return true; + } + + return false; + } + } + +} diff --git a/Source/AkiSupport/Custom/CustomAI/PmcFoundInRaidEquipment.cs b/Source/AkiSupport/Custom/CustomAI/PmcFoundInRaidEquipment.cs new file mode 100644 index 000000000..266204c3d --- /dev/null +++ b/Source/AkiSupport/Custom/CustomAI/PmcFoundInRaidEquipment.cs @@ -0,0 +1,157 @@ +using BepInEx.Logging; +using EFT.InventoryLogic; +using EFT; +using System.Collections.Generic; +using System.Linq; + +namespace StayInTarkov.AkiSupport.Custom.CustomAI +{ + /// + /// Created by: SPT-Aki + /// Link: https://dev.sp-tarkov.com/SPT/Modules/src/branch/master/project/SPT.Custom/CustomAI/PmcFoundInRaidEquipment.cs + /// + public class PmcFoundInRaidEquipment + { + private static readonly string magazineId = "5448bc234bdc2d3c308b4569"; + private static readonly string drugId = "5448f3a14bdc2d27728b4569"; + private static readonly string mediKitItem = "5448f39d4bdc2d0a728b4568"; + private static readonly string medicalItemId = "5448f3ac4bdc2dce718b4569"; + private static readonly string injectorItemId = "5448f3a64bdc2d60728b456a"; + private static readonly string throwableItemId = "543be6564bdc2df4348b4568"; + private static readonly string ammoItemId = "5485a8684bdc2da71d8b4567"; + private static readonly string weaponId = "5422acb9af1c889c16000029"; + private static readonly string moneyId = "543be5dd4bdc2deb348b4569"; + private static readonly string armorPlate = "644120aa86ffbe10ee032b6f"; + private static readonly string builtInInserts = "65649eb40bf0ed77b8044453"; + private static readonly List nonFiRItems = new List() { magazineId, drugId, mediKitItem, medicalItemId, injectorItemId, throwableItemId, ammoItemId, armorPlate, builtInInserts }; + + private static readonly string pistolId = "5447b5cf4bdc2d65278b4567"; + private static readonly string smgId = "5447b5e04bdc2d62278b4567"; + private static readonly string assaultRifleId = "5447b5f14bdc2d61278b4567"; + private static readonly string assaultCarbineId = "5447b5fc4bdc2d87278b4567"; + private static readonly string shotgunId = "5447b6094bdc2dc3278b4567"; + private static readonly string marksmanRifleId = "5447b6194bdc2d67278b4567"; + private static readonly string sniperRifleId = "5447b6254bdc2dc3278b4568"; + private static readonly string machinegunId = "5447bed64bdc2d97278b4568"; + private static readonly string grenadeLauncherId = "5447bedf4bdc2d87278b4568"; + private static readonly string knifeId = "5447e1d04bdc2dff2f8b4567"; + + private static readonly List weaponTypeIds = new List() { pistolId, smgId, assaultRifleId, assaultCarbineId, shotgunId, marksmanRifleId, sniperRifleId, machinegunId, grenadeLauncherId, knifeId }; + private static readonly List nonFiRPocketLoot = new List { moneyId, throwableItemId, ammoItemId, magazineId, medicalItemId, mediKitItem, injectorItemId, drugId }; + private readonly ManualLogSource logger; + + public PmcFoundInRaidEquipment(ManualLogSource logger) + { + this.logger = logger; + } + + public void ConfigurePMCFindInRaidStatus(BotOwner ___botOwner_0) + { + // Must run before the container loot code, otherwise backpack loot is not FiR + MakeEquipmentNotFiR(___botOwner_0); + + // Get inventory items that hold other items (backpack/rig/pockets/armor) + IReadOnlyList containerGear = ___botOwner_0.Profile.Inventory.Equipment.ContainerSlots; + var nonFiRRootItems = new List(); + foreach (var container in containerGear) + { + var firstItem = container.Items.FirstOrDefault(); + foreach (var item in container.ContainedItem.GetAllItems()) + { + // Skip items that match container (array has itself as an item) + if (item.Id == firstItem?.Id) + { + //this.logger.LogError($"Skipping item {item.Id} {item.Name} as its same as container {container.FullId}"); + continue; + } + + // Don't add FiR to any item inside armor vest, e.g. plates/soft inserts + if (container.Name == "ArmorVest") + { + continue; + } + + // Don't add FiR to any item inside head gear, e.g. soft inserts/nvg/lights + if (container.Name == "Headwear") + { + continue; + } + + // Don't add FiR to tacvest items PMC usually brings into raid (meds/mags etc) + if (container.Name == "TacticalVest" && nonFiRItems.Any(item.Template._parent.Contains)) + { + //this.logger.LogError($"Skipping item {item.Id} {item.Name} as its on the item type blacklist"); + continue; + } + + // Don't add FiR to weapons in backpack (server sometimes adds pre-made weapons to backpack to simulate PMCs looting bodies) + if (container.Name == "Backpack" && weaponTypeIds.Any(item.Template._parent.Contains)) + { + // Add weapon root to list for later use + nonFiRRootItems.Add(item); + //this.logger.LogError($"Skipping item {item.Id} {item.Name} as its on the item type blacklist"); + continue; + } + + // Don't add FiR to grenades/mags/ammo/meds in pockets + if (container.Name == "Pockets" && nonFiRPocketLoot.Exists(item.Template._parent.Contains)) + { + //this.logger.LogError($"Skipping item {item.Id} {item.Name} as its on the item type blacklist"); + continue; + } + + // Check for mods of weapons in backpacks + var isChildOfEquippedNonFiRItem = nonFiRRootItems.Any(nonFiRRootItem => item.IsChildOf(nonFiRRootItem)); + if (isChildOfEquippedNonFiRItem) + { + continue; + } + + //Logger.LogError($"flagging item FiR: {item.Id} {item.Name} _parent: {item.Template._parent}"); + item.SpawnedInSession = true; + } + } + + // Set dogtag as FiR + var dogtag = ___botOwner_0.Profile.Inventory.GetItemsInSlots(new EquipmentSlot[] { EquipmentSlot.Dogtag }).FirstOrDefault(); + if (dogtag != null) + { + dogtag.SpawnedInSession = true; + } + } + + + private void MakeEquipmentNotFiR(BotOwner ___botOwner_0) + { + var additionalItems = ___botOwner_0.Profile.Inventory.GetItemsInSlots(new EquipmentSlot[] + { EquipmentSlot.Backpack, + EquipmentSlot.FirstPrimaryWeapon, + EquipmentSlot.SecondPrimaryWeapon, + EquipmentSlot.TacticalVest, + EquipmentSlot.ArmorVest, + EquipmentSlot.Scabbard, + EquipmentSlot.Eyewear, + EquipmentSlot.Headwear, + EquipmentSlot.Earpiece, + EquipmentSlot.ArmBand, + EquipmentSlot.FaceCover, + EquipmentSlot.Holster, + EquipmentSlot.SecuredContainer + }); + + foreach (var item in additionalItems) + { + // Some items are null, probably because bot doesnt have that particular slot on them + if (item == null) + { + continue; + } + + //Logger.LogError($"flagging item FiR: {item.Id} {item.Name} _parent: {item.Template._parent}"); + item.SpawnedInSession = false; + } + } + + } + +} diff --git a/Source/AkiSupport/Custom/CustomAiPatch.cs b/Source/AkiSupport/Custom/CustomAiPatch.cs new file mode 100644 index 000000000..646026eb8 --- /dev/null +++ b/Source/AkiSupport/Custom/CustomAiPatch.cs @@ -0,0 +1,122 @@ +using Comfort.Common; +using EFT; +using HarmonyLib; +using StayInTarkov.AkiSupport.Custom.CustomAI; +using System; +using System.Reflection; + +namespace StayInTarkov.AkiSupport.Custom +{ + /// + /// Created by: SPT-Aki + /// Link: https://dev.sp-tarkov.com/SPT/Modules/src/branch/master/project/SPT.Custom/Patches/CustomAiPatch.cs + /// + public class CustomAiPatch : ModulePatch + { + private static readonly PmcFoundInRaidEquipment pmcFoundInRaidEquipment = new PmcFoundInRaidEquipment(Logger); + private static readonly AIBrainSpawnWeightAdjustment aIBrainSpawnWeightAdjustment = new AIBrainSpawnWeightAdjustment(Logger); + + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(StandartBotBrain), nameof(StandartBotBrain.Activate)); + } + + /// + /// Get a randomly picked wildspawntype from server and change PMC bot to use it, this ensures the bot is generated with that random type altering its behaviour + /// Postfix will adjust it back to original type + /// + /// state to save for postfix to use later + /// + /// botOwner_0 property + [PatchPrefix] + private static bool PatchPrefix(out WildSpawnType __state, StandartBotBrain __instance, BotOwner ___botOwner_0) + { + var player = Singleton.Instance.MainPlayer; + ___botOwner_0.Profile.Info.Settings.Role = FixAssaultGroupPmcsRole(___botOwner_0); + __state = ___botOwner_0.Profile.Info.Settings.Role; // Store original type in state param to allow access in PatchPostFix() + try + { + string currentMapName = GetCurrentMap(); + if (AiHelpers.BotIsPlayerScav(__state, ___botOwner_0.Profile.Info.Nickname)) + { + ___botOwner_0.Profile.Info.Settings.Role = aIBrainSpawnWeightAdjustment.GetRandomisedPlayerScavType(___botOwner_0, currentMapName); + + return true; // Do original + } + + if (AiHelpers.BotIsNormalAssaultScav(__state, ___botOwner_0)) + { + ___botOwner_0.Profile.Info.Settings.Role = aIBrainSpawnWeightAdjustment.GetAssaultScavWildSpawnType(___botOwner_0, currentMapName); + + return true; // Do original + } + + if (AiHelpers.BotIsSptPmc(__state, ___botOwner_0)) + { + // Bot has inventory equipment + if (___botOwner_0.Profile?.Inventory?.Equipment != null) + { + pmcFoundInRaidEquipment.ConfigurePMCFindInRaidStatus(___botOwner_0); + } + + ___botOwner_0.Profile.Info.Settings.Role = aIBrainSpawnWeightAdjustment.GetPmcWildSpawnType(___botOwner_0, ___botOwner_0.Profile.Info.Settings.Role, currentMapName); + } + } + catch (Exception ex) + { + Logger.LogError($"Error running CustomAiPatch PatchPrefix(): {ex.Message}"); + Logger.LogError(ex.StackTrace); + } + + return true; // Do original + } + + /// + /// the client sometimes replaces PMC roles with 'assaultGroup', give PMCs their original role back (sptBear/sptUsec) + /// + /// WildSpawnType + private static WildSpawnType FixAssaultGroupPmcsRole(BotOwner botOwner) + { + if (botOwner.Profile.Info.IsStreamerModeAvailable && botOwner.Profile.Info.Settings.Role == WildSpawnType.assaultGroup) + { + Logger.LogError($"Broken PMC found: {botOwner.Profile.Nickname}, was {botOwner.Profile.Info.Settings.Role}"); + + // Its a PMC, figure out what the bot originally was and return it + return botOwner.Profile.Info.Side == EPlayerSide.Bear + ? (WildSpawnType) WildSpawnTypePrePatcher.sptBearValue + : (WildSpawnType) WildSpawnTypePrePatcher.sptUsecValue; + } + + // Not broken pmc, return original role + return botOwner.Profile.Info.Settings.Role; + } + + /// + /// Revert prefix change, get bots type back to what it was before changes + /// + /// Saved state from prefix patch + /// botOwner_0 property + [PatchPostfix] + private static void PatchPostFix(WildSpawnType __state, BotOwner ___botOwner_0) + { + if (AiHelpers.BotIsSptPmc(__state, ___botOwner_0)) + { + // Set spt bot bot back to original type + ___botOwner_0.Profile.Info.Settings.Role = __state; + } + else if (AiHelpers.BotIsPlayerScav(__state, ___botOwner_0.Profile.Info.Nickname)) + { + // Set pscav back to original type + ___botOwner_0.Profile.Info.Settings.Role = __state; + } + } + + private static string GetCurrentMap() + { + var gameWorld = Singleton.Instance; + + return gameWorld.MainPlayer.Location; + } + } + +} diff --git a/Source/AkiSupport/Custom/PmcFirstAidPatch.cs b/Source/AkiSupport/Custom/PmcFirstAidPatch.cs index 3986107b3..3b921139f 100644 --- a/Source/AkiSupport/Custom/PmcFirstAidPatch.cs +++ b/Source/AkiSupport/Custom/PmcFirstAidPatch.cs @@ -16,7 +16,7 @@ public class PmcFirstAidPatch : ModulePatch protected override MethodBase GetTargetMethod() { - return ReflectionHelpers.GetMethodForType(typeof(FirstAid), methodName); + return ReflectionHelpers.GetMethodForType(typeof(GFirstAid), methodName); } [PatchPrefix] diff --git a/Source/AkiSupport/Custom/QTEPatch.cs b/Source/AkiSupport/Custom/QTEPatch.cs index 01fc243db..19eb3a5b7 100644 --- a/Source/AkiSupport/Custom/QTEPatch.cs +++ b/Source/AkiSupport/Custom/QTEPatch.cs @@ -17,7 +17,7 @@ public class QTEPatch : ModulePatch [PatchPostfix] private static void PatchPostfix(HideoutPlayerOwner __instance) { - AkiBackendCommunication.Instance.PostJson("/client/hideout/workout", new + AkiBackendCommunication.Instance.PostJsonBLOCKING("/client/hideout/workout", new { skills = __instance.HideoutPlayer.Skills, effects = __instance.HideoutPlayer.HealthController.BodyPartEffects diff --git a/Source/AkiSupport/Custom/SettingsLocationPatch.cs b/Source/AkiSupport/Custom/SettingsLocationPatch.cs index 2482cfc3f..d2b1ecd9e 100644 --- a/Source/AkiSupport/Custom/SettingsLocationPatch.cs +++ b/Source/AkiSupport/Custom/SettingsLocationPatch.cs @@ -20,10 +20,10 @@ public static void Enable() Directory.CreateDirectory(_sptPath); // Screenshot - FieldInfo DocumentsSettings = ReflectionHelpers.GetFieldFromType(typeof(SettingsManager), "string_0"); + FieldInfo DocumentsSettings = ReflectionHelpers.GetFieldFromType(typeof(SharedGameSettingsClass), "string_0"); // Game Settings - FieldInfo ApplicationDataSettings = ReflectionHelpers.GetFieldFromType(typeof(SettingsManager), "string_1"); + FieldInfo ApplicationDataSettings = ReflectionHelpers.GetFieldFromType(typeof(SharedGameSettingsClass), "string_1"); // They are 'static' variables, not needed to give a instance. DocumentsSettings.SetValue(null, _sptPath); diff --git a/Source/AkiSupport/Singleplayer/AkiSingleplayerPlugin.cs b/Source/AkiSupport/Singleplayer/AkiSingleplayerPlugin.cs index b01626ced..40553ba9c 100644 --- a/Source/AkiSupport/Singleplayer/AkiSingleplayerPlugin.cs +++ b/Source/AkiSupport/Singleplayer/AkiSingleplayerPlugin.cs @@ -13,7 +13,7 @@ namespace StayInTarkov.AkiSupport.Singleplayer /// Credit SPT-Aki team /// Paulov. I have removed a lot of unused patches /// - [BepInPlugin("com.spt-aki.singleplayer", "AKI.Singleplayer", "1.0.0.0")] + [BepInPlugin("com.stayintarkov.aki.sp", "SIT.Aki.SPT", "1.0.0.0")] class AkiSingleplayerPlugin : BaseUnityPlugin { public void Awake() @@ -29,7 +29,6 @@ public void Awake() new GetNewBotTemplatesPatch().Enable(); new DogtagPatch().Enable(); new PostRaidHealingPricePatch().Enable(); - new EndByTimerPatch().Enable(); new PostRaidHealScreenPatch().Enable(); new LighthouseBridgePatch().Enable(); new LighthouseTransmitterPatch().Enable(); diff --git a/Source/AkiSupport/Singleplayer/Patches/MainMenu/AmmoUsedCounterPatch.cs b/Source/AkiSupport/Singleplayer/Patches/MainMenu/AmmoUsedCounterPatch.cs index 048181c23..58b670c1f 100644 --- a/Source/AkiSupport/Singleplayer/Patches/MainMenu/AmmoUsedCounterPatch.cs +++ b/Source/AkiSupport/Singleplayer/Patches/MainMenu/AmmoUsedCounterPatch.cs @@ -23,7 +23,7 @@ private static void PatchPostfix(Player __instance) { if (__instance.IsYourPlayer) { - __instance.Profile.EftStats.SessionCounters.AddLong(1L, ASessionCounterManager.AmmoUsed); + __instance.Profile.EftStats.SessionCounters.AddLong(1L, SessionCounterTypesAbstractClass.AmmoUsed); } } } diff --git a/Source/AkiSupport/Singleplayer/Patches/MainMenu/ArmorDamageCounterPatch.cs b/Source/AkiSupport/Singleplayer/Patches/MainMenu/ArmorDamageCounterPatch.cs index 16f15ea13..3c1118340 100644 --- a/Source/AkiSupport/Singleplayer/Patches/MainMenu/ArmorDamageCounterPatch.cs +++ b/Source/AkiSupport/Singleplayer/Patches/MainMenu/ArmorDamageCounterPatch.cs @@ -52,7 +52,7 @@ private static void PatchPostfix(DamageInfo damageInfo) if (template is AmmoTemplate bulletTemplate) { float absorbedDamage = (float)Math.Round(bulletTemplate.Damage - damageInfo.Damage); - damageInfo.Player.iPlayer.Profile.EftStats.SessionCounters.AddFloat(absorbedDamage, ASessionCounterManager.CauseArmorDamage); + damageInfo.Player.iPlayer.Profile.EftStats.SessionCounters.AddFloat(absorbedDamage, SessionCounterTypesAbstractClass.CauseArmorDamage); } } } diff --git a/Source/AkiSupport/Singleplayer/Patches/Quests/EndByTimerPatch.cs b/Source/AkiSupport/Singleplayer/Patches/Quests/EndByTimerPatch.cs deleted file mode 100644 index 6b19759db..000000000 --- a/Source/AkiSupport/Singleplayer/Patches/Quests/EndByTimerPatch.cs +++ /dev/null @@ -1,53 +0,0 @@ -using EFT; -using System.Reflection; -using HarmonyLib; -using StayInTarkov.Networking; - -namespace StayInTarkov.AkiSupport.Singleplayer.Patches.Quests -{ - /// - /// Credit SPT-Aki team - /// Link: https://dev.sp-tarkov.com/SPT-AKI/Modules/src/branch/master/project/Aki.SinglePlayer/Patches/Quests/EndByTimerPatch.cs - /// Modified by: KWJimWails. Converted to use AkiBackendCommunication - /// Having the raid timer reach zero results in a successful extract, - /// this patch makes it so letting the time reach zero results in a MIA result - /// - public class EndByTimerPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(BaseLocalGame), nameof(BaseLocalGame.Stop)); - } - - // Unused, but left here in case patch breaks and finding the intended method is difficult - private static bool IsStopRaidMethod(MethodInfo mi) - { - var parameters = mi.GetParameters(); - return (parameters.Length == 4 - && parameters[0].Name == "profileId" - && parameters[1].Name == "exitStatus" - && parameters[2].Name == "exitName" - && parameters[3].Name == "delay" - && parameters[0].ParameterType == typeof(string) - && parameters[1].ParameterType == typeof(ExitStatus) - && parameters[2].ParameterType == typeof(string) - && parameters[3].ParameterType == typeof(float)); - } - - [PatchPrefix] - private static bool PrefixPatch(ref ExitStatus exitStatus, ref string exitName) - { - var isParsed = bool.TryParse(AkiBackendCommunication.Instance.GetJson("/singleplayer/settings/raid/endstate"), out bool MIAOnRaidEnd); - if (isParsed) - { - // No extract name and successful, its a MIA - if (MIAOnRaidEnd && string.IsNullOrEmpty(exitName?.Trim()) && exitStatus == ExitStatus.Survived) - { - exitStatus = ExitStatus.MissingInAction; - exitName = null; - } - } - return true; // Do original - } - } -} diff --git a/Source/AkiSupport/Singleplayer/Patches/RaidFix/LabsKeycardRemovalPatch.cs b/Source/AkiSupport/Singleplayer/Patches/RaidFix/LabsKeycardRemovalPatch.cs index 8ba3d449b..5312aead1 100644 --- a/Source/AkiSupport/Singleplayer/Patches/RaidFix/LabsKeycardRemovalPatch.cs +++ b/Source/AkiSupport/Singleplayer/Patches/RaidFix/LabsKeycardRemovalPatch.cs @@ -43,7 +43,7 @@ private static void PatchPostfix() } var inventoryController = Traverse.Create(player).Field("_inventoryController").Value; - ItemMovementHandler.Remove(accessCardItem, inventoryController, false, true); + InteractionsHandlerClass.Remove(accessCardItem, inventoryController, false, true); } } } \ No newline at end of file diff --git a/Source/AkiSupport/Singleplayer/Patches/ScavMode/IsHostileToEverybodyPatch.cs b/Source/AkiSupport/Singleplayer/Patches/ScavMode/IsHostileToEverybodyPatch.cs index 6098d40d1..d409c19aa 100644 --- a/Source/AkiSupport/Singleplayer/Patches/ScavMode/IsHostileToEverybodyPatch.cs +++ b/Source/AkiSupport/Singleplayer/Patches/ScavMode/IsHostileToEverybodyPatch.cs @@ -7,7 +7,7 @@ public class IsHostileToEverybodyPatch : ModulePatch { protected override MethodBase GetTargetMethod() { - var desiredType = typeof(ServerBotSettingsClass); + var desiredType = typeof(BotSettingsRepoClass); var desiredMethod = desiredType.GetMethod("IsHostileToEverybody", BindingFlags.Public | BindingFlags.Static); return desiredMethod; @@ -16,7 +16,7 @@ protected override MethodBase GetTargetMethod() [PatchPrefix] private static bool PatchPrefix(WildSpawnType role, ref bool __result) { - if (role == WildSpawnType.sptUsec || role == WildSpawnType.sptBear) + if ((int)role == 47 || (int)role == 48) { __result = true; return false; diff --git a/Source/AkiSupport/Singleplayer/Patches/ScavMode/ScavLateStartPatch.cs b/Source/AkiSupport/Singleplayer/Patches/ScavMode/ScavLateStartPatch.cs index 4276ee14b..595c276b5 100644 --- a/Source/AkiSupport/Singleplayer/Patches/ScavMode/ScavLateStartPatch.cs +++ b/Source/AkiSupport/Singleplayer/Patches/ScavMode/ScavLateStartPatch.cs @@ -56,7 +56,7 @@ private static bool PatchPrefix(ref RaidSettings ____raidSettings) // Create request and send to server, parse response var request = new RaidTimeRequest(____raidSettings.Side, currentMapId); - var json = AkiBackendCommunication.Instance.PostJson("/singleplayer/settings/getRaidTime", Json.Serialize(request)); + var json = AkiBackendCommunication.Instance.PostJsonBLOCKING("/singleplayer/settings/getRaidTime", Json.Serialize(request)); var serverResult = Json.Deserialize(json); // Capture the changes that will be made to the raid so they can be easily accessed by modders diff --git a/Source/AkiSupport/Singleplayer/Patches/ScavMode/ScavSellAllRequestPatch.cs b/Source/AkiSupport/Singleplayer/Patches/ScavMode/ScavSellAllRequestPatch.cs index 3dfe7b12d..42d2dd963 100644 --- a/Source/AkiSupport/Singleplayer/Patches/ScavMode/ScavSellAllRequestPatch.cs +++ b/Source/AkiSupport/Singleplayer/Patches/ScavMode/ScavSellAllRequestPatch.cs @@ -19,7 +19,7 @@ protected override MethodBase GetTargetMethod() { // We want to find a type that contains `SellAllFromSavage` but doesn't extend from `IBackendStatus` //Type targetType = StayInTarkovHelperConstants.EftTypes.SingleCustom(IsTargetType); - Type targetType = typeof(TradingBackend1); + Type targetType = typeof(TradingBackend); Logger.LogDebug($"{this.GetType().Name} Type: {targetType?.Name}"); diff --git a/Source/AssemblyInfo.cs b/Source/AssemblyInfo.cs index dedee262f..426e7e6e6 100644 --- a/Source/AssemblyInfo.cs +++ b/Source/AssemblyInfo.cs @@ -31,5 +31,5 @@ // Revision // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.10.*")] +[assembly: AssemblyVersion("1.11.*")] diff --git a/Source/Configuration/PluginConfigSettings.cs b/Source/Configuration/PluginConfigSettings.cs index 44a2df702..3cc4c2c1e 100644 --- a/Source/Configuration/PluginConfigSettings.cs +++ b/Source/Configuration/PluginConfigSettings.cs @@ -3,6 +3,7 @@ using StayInTarkov.Networking; using System; using System.Net; +using UnityEngine; #nullable enable @@ -38,37 +39,34 @@ public void GetSettings() } + internal sealed class ConfigurationManagerAttributes + { + public bool? IsAdvanced = false; + public bool? ShowRangeAsPercent; + } + public class SITAdvancedSettings { public const string Advanced = "Advanced"; public ConfigFile Config { get; } public ManualLogSource Logger { get; } - public bool SETTING_EnableSITGC - { - get - { - return StayInTarkovPlugin.Instance.Config.Bind - (Advanced, "EnableSITGC", false, new ConfigDescription("Enable SIT's own Garbage Collector")).Value; - } - } - - public uint SETTING_SITGCMemoryThreshold - { - get - { - return StayInTarkovPlugin.Instance.Config.Bind - (Advanced, "SITGCMemoryThreshold", 90u, new ConfigDescription("SIT's Garbage Collector. System Memory % before SIT forces a Garbage Collection.")).Value; - } - } + public bool SETTING_EnableSITGC { get; set; } + public uint SETTING_SITGCMemoryThreshold { get; set; } + public uint SETTING_SITGCMemoryCheckTime { get; set; } public SITAdvancedSettings(ManualLogSource logger, ConfigFile config) { Logger = logger; Config = config; - _ = SETTING_EnableSITGC; - _ = SETTING_SITGCMemoryThreshold; + SETTING_EnableSITGC = StayInTarkovPlugin.Instance.Config.Bind + (Advanced, "EnableSITGC", false, new ConfigDescription("Enable SIT's own Garbage Collector")).Value; + SETTING_SITGCMemoryThreshold = StayInTarkovPlugin.Instance.Config.Bind + (Advanced, "SITGCMemoryThreshold", 90u, new ConfigDescription("System Memory usage % limit, above that, SIT forces the run of Garbage Collection.", new AcceptableValueRange(50u, 95u), new ConfigurationManagerAttributes { IsAdvanced = true, ShowRangeAsPercent = true })).Value; + SETTING_SITGCMemoryCheckTime = StayInTarkovPlugin.Instance.Config.Bind + (Advanced, "SITGCMemoryCheckTime", 300u, new ConfigDescription("Set how many seconds to wait between SIT's Garbage Collector checks.", new AcceptableValueRange(60u, 900u), new ConfigurationManagerAttributes { IsAdvanced = true })).Value; + } } @@ -99,6 +97,8 @@ public bool SETTING_DEBUGShowPlayerList public bool SETTING_HeadshotsAlwaysKill { get; set; } = false; public bool SETTING_ShowFeed { get; set; } = true; public bool SETTING_ShowSITStatistics { get; set; } = true; + public ConfigEntry SETTING_PressToExtractKey { get; private set; } + public ConfigEntry SETTING_PressToForceExtractKey { get; private set; } public bool ShowPing { get; set; } = true; public int SITWebSocketPort { get; set; } = 6970; public int SITNatHelperPort { get; set; } = 6971; @@ -114,17 +114,19 @@ public bool SETTING_DEBUGShowPlayerList public bool ArenaMode { get; set; } = false; public bool EnableAISpawnWaveSystem { get; set; } = true; + public int BotTeleportDistance { get; set; } = 40; + public bool ForceHighPingMode { get; set; } = false; public bool RunThroughOnServerStop { get; set; } = true; public int WaitingTimeBeforeStart { get; private set; } - public int BlackScreenOnDeathTime + public float BlackScreenOnDeathTime { get { return StayInTarkovPlugin.Instance.Config.Bind - ("Coop", "BlackScreenOnDeathTime", 500, new ConfigDescription("How long to wait until your death waits to become a Free Camera")).Value; + ("Coop", "BlackScreenOnDeathTime", 5F, new ConfigDescription("How long to wait after death until you become a Free Camera")).Value; } } @@ -143,6 +145,11 @@ public void GetSettings() SETTING_ShowFeed = StayInTarkovPlugin.Instance.Config.Bind ("Coop", "ShowFeed", true, new ConfigDescription("Enable the feed on the bottom right of the screen which shows player/bot spawns, kills, etc.")).Value; + SETTING_PressToExtractKey = StayInTarkovPlugin.Instance.Config.Bind + ("Coop", "PressToExtractKey", new KeyboardShortcut(KeyCode.F8), new ConfigDescription("The key you need to press to leave the raid.")); + + SETTING_PressToForceExtractKey = StayInTarkovPlugin.Instance.Config.Bind + ("Coop", "PressToForceExtractKey", new KeyboardShortcut(KeyCode.F7), new ConfigDescription("The key you need to press to FORCE leave the raid.")); AllPlayersSpawnTogether = StayInTarkovPlugin.Instance.Config.Bind ("Coop", "AllPlayersSpawnTogether", true, new ConfigDescription("Whether to spawn all players in the same place")).Value; @@ -159,6 +166,9 @@ public void GetSettings() WaitingTimeBeforeStart = Config.Bind("Coop", "WaitingTimeBeforeStart", 120 , new ConfigDescription("Time in seconds to wait for players before starting the game automatically")).Value; + BotTeleportDistance = Config.Bind("Coop", "BotTeleportDistance", 40 + , new ConfigDescription("Distance greater than X meters to teleport bots to player")).Value; + SETTING_ShowSITStatistics = StayInTarkovPlugin.Instance.Config.Bind ("Coop", "ShowSITStatistics", true, new ConfigDescription("Enable the SIT statistics on the top left of the screen which shows ping, player count, etc.")).Value; diff --git a/Source/Coop/AI/BotDespawnPatch.cs b/Source/Coop/AI/BotDespawnPatch.cs new file mode 100644 index 000000000..4d10716b6 --- /dev/null +++ b/Source/Coop/AI/BotDespawnPatch.cs @@ -0,0 +1,47 @@ +using EFT; +using StayInTarkov.Coop.Components.CoopGameComponents; +using StayInTarkov.Coop.Matchmaker; +using StayInTarkov.Coop.NetworkPacket.AI; +using StayInTarkov.Coop.NetworkPacket.Player.Health; +using StayInTarkov.Networking; +using System.Reflection; + +namespace StayInTarkov.Coop.AI +{ + /// + /// Goal: Despawning bots between Server and Client is broken, this patch aims to fix that by patching DestroyInfo to send a packet once it has ran. + /// + internal class BotDespawnPatch : ModulePatch + { + private static readonly string methodName = "DestroyInfo"; + + protected override MethodBase GetTargetMethod() + { + return typeof(BotsController).GetMethod(methodName); + } + + //[PatchPrefix] + //private static bool PatchPrefix(EFT.Player player) + //{ + // return false; + //} + + [PatchPostfix] + public static void Postfix(EFT.Player player) + { + if (SITMatchmaking.IsClient) + return; + + if (!SITGameComponent.TryGetCoopGameComponent(out var coopGameComponent)) + return; + + if (coopGameComponent.ProfileIdsAI.Contains(player.ProfileId)) + coopGameComponent.ProfileIdsAI.Remove(player.ProfileId); + + DespawnAIPacket packet = new(); + packet.AIProfileId = player.ProfileId; + + GameClient.SendData(packet.Serialize()); + } + } +} diff --git a/Source/Coop/Airdrops/SITAirdropsManager.cs b/Source/Coop/Airdrops/SITAirdropsManager.cs index 70cc619c4..61816ab35 100644 --- a/Source/Coop/Airdrops/SITAirdropsManager.cs +++ b/Source/Coop/Airdrops/SITAirdropsManager.cs @@ -3,19 +3,17 @@ using BepInEx.Logging; using Comfort.Common; using EFT; -using EFT.Game.Spawning; -using EFT.Interactive; using StayInTarkov; using StayInTarkov.AkiSupport.Airdrops; using StayInTarkov.AkiSupport.Airdrops.Models; using StayInTarkov.AkiSupport.Airdrops.Utils; using StayInTarkov.Coop.Components.CoopGameComponents; using StayInTarkov.Coop.Matchmaker; -using StayInTarkov.Coop.NetworkPacket; -using StayInTarkov.Coop.Web; +using StayInTarkov.Coop.NetworkPacket.Airdrop; using StayInTarkov.Networking; +using System; using System.Collections; -using System.Collections.Generic; +using System.Threading.Tasks; using UnityEngine; namespace Aki.Custom.Airdrops @@ -28,16 +26,23 @@ namespace Aki.Custom.Airdrops public class SITAirdropsManager : MonoBehaviour { private AirdropPlane airdropPlane; - public AirdropBox AirdropBox { get; private set; } private ItemFactoryUtil factory; - public bool isFlareDrop; + private float distanceTravelled = 0; + + private DateTime LastSyncTime { get; set; } + private ManualLogSource Logger { get; } = BepInEx.Logging.Logger.CreateLogSource(nameof(SITAirdropsManager)); + + public AirdropBox AirdropBox { get; private set; } public AirdropParametersModel AirdropParameters { get; set; } - private ManualLogSource Logger { get; set; } + public bool ClientPlaneSpawned { get; private set; } + public AirdropLootResultModel ClientAirdropLootResultModel { get; private set; } + public AirdropConfigModel ClientAirdropConfigModel { get; private set; } + public bool ClientLootBuilt { get; private set; } + public bool IsFlareDrop { get; set; } void Awake() { - Logger = BepInEx.Logging.Logger.CreateLogSource("SITAirdropsManager"); Logger.LogInfo("Awake"); Singleton.Create(this); } @@ -67,7 +72,7 @@ public async void Start() // The server will generate stuff ready for the packet - AirdropParameters = AirdropUtil.InitAirdropParams(gameWorld, isFlareDrop); + AirdropParameters = await AirdropUtil.InitAirdropParams(gameWorld, IsFlareDrop); if (!AirdropParameters.AirdropAvailable) { @@ -98,10 +103,9 @@ public async void Start() SetDistanceToDrop(); - BuildLootContainer(AirdropParameters.Config); + await BuildLootContainer(AirdropParameters.Config); StartCoroutine(SendParamsToClients()); - } public IEnumerator SendParamsToClients() @@ -112,11 +116,11 @@ public IEnumerator SendParamsToClients() yield return new WaitForSeconds(AirdropParameters.TimeToStart); Logger.LogDebug("Sending Airdrop Params"); - var packet = new Dictionary(); - packet.Add("serverId", SITGameComponent.GetServerId()); - packet.Add("m", "AirdropPacket"); - packet.Add("model", AirdropParameters); - GameClient.SendData(packet.SITToJson()); + AirdropPacket airdropPacket = new() + { + AirdropParametersModelJson = AirdropParameters.SITToJson() + }; + GameClient.SendData(airdropPacket.Serialize()); yield break; } @@ -147,16 +151,11 @@ public async void FixedUpdate() factory.BuildContainer(AirdropBox.Container, ClientAirdropConfigModel, ClientAirdropLootResultModel.DropType); factory.AddLoot(AirdropBox.Container, ClientAirdropLootResultModel); - if (AirdropBox.Container != null) + + if (AirdropBox.Container != null && SITGameComponent.TryGetCoopGameComponent(out SITGameComponent coopGameComponent)) { - if (SITGameComponent.TryGetCoopGameComponent(out var coopGameComponent)) - { - List oldInteractiveObjectList = new List(coopGameComponent.ListOfInteractiveObjects) - { - AirdropBox.Container - }; - coopGameComponent.ListOfInteractiveObjects = [.. oldInteractiveObjectList]; - } + Logger.LogDebug($"Adding Airdrop box with id {AirdropBox.Container.Id}"); + coopGameComponent.WorldnteractiveObjects.TryAdd(AirdropBox.Container.Id, AirdropBox.Container); } } @@ -197,6 +196,20 @@ public async void FixedUpdate() StartBox(); } + if (AirdropParameters.BoxSpawned && !SITMatchmaking.IsClient) + { + if (this.LastSyncTime < DateTime.Now.AddSeconds(-1)) + { + this.LastSyncTime = DateTime.Now; + + AirdropBoxPositionSyncPacket packet = new() + { + Position = AirdropBox.transform.position + }; + GameClient.SendData(packet.Serialize()); + } + } + if (distanceTravelled < AirdropParameters.DistanceToTravel) { distanceTravelled += Time.deltaTime * AirdropParameters.Config.PlaneSpeed; @@ -210,8 +223,6 @@ public async void FixedUpdate() } } - float distanceTravelled = 0; - private void StartPlane() { airdropPlane.gameObject.SetActive(true); @@ -227,50 +238,36 @@ private void StartBox() AirdropBox.StartCoroutine(AirdropBox.DropCrate(dropPos)); } - public bool ClientPlaneSpawned { get; private set; } - public AirdropLootResultModel ClientAirdropLootResultModel { get; private set; } - public AirdropConfigModel ClientAirdropConfigModel { get; private set; } - public bool ClientLootBuilt { get; private set; } - - private void BuildLootContainer(AirdropConfigModel config) + private async Task BuildLootContainer(AirdropConfigModel config) { - if (SITMatchmaking.IsClient) + if (!SITMatchmaking.IsServer) return; - var lootData = factory.GetLoot(); + // Get the lootData for this Raid + AirdropLootResultModel lootData = await factory.GetLoot() ?? throw new Exception("Airdrops. Tried to BuildLootContainer without any Loot."); - // Get the lootData. Sent to Clients. - if (SITMatchmaking.IsServer) + // Send the lootData to Clients. + AirdropLootPacket airdropLootPacket = new() { - var packet = new Dictionary(); - packet.Add("serverId", SITGameComponent.GetServerId()); - packet.Add("m", "AirdropLootPacket"); - packet.Add("config", config); - packet.Add("result", lootData); - GameClient.SendData(packet.SITToJson()); - } + AirdropLootResultModelJson = lootData.SITToJson(), + AirdropConfigModelJson = config.SITToJson() + }; + GameClient.SendData(airdropLootPacket.Serialize()); - if (lootData == null) - throw new System.Exception("Airdrops. Tried to BuildLootContainer without any Loot."); - factory.BuildContainer(AirdropBox.Container, config, lootData.DropType); factory.AddLoot(AirdropBox.Container, lootData); ClientLootBuilt = true; - if (AirdropBox.Container != null) + + if (AirdropBox.Container != null && SITGameComponent.TryGetCoopGameComponent(out SITGameComponent coopGameComponent)) { - if (SITGameComponent.TryGetCoopGameComponent(out var coopGameComponent)) - { - List oldInteractiveObjectList = new List(coopGameComponent.ListOfInteractiveObjects) - { - AirdropBox.Container - }; - coopGameComponent.ListOfInteractiveObjects = [.. oldInteractiveObjectList]; - } + Logger.LogDebug($"Adding Airdrop box with id {AirdropBox.Container.Id}"); + coopGameComponent.WorldnteractiveObjects.TryAdd(AirdropBox.Container.Id, AirdropBox.Container); } } public void ReceiveBuildLootContainer(AirdropLootResultModel lootData, AirdropConfigModel config) { + Logger.LogDebug(nameof(ReceiveBuildLootContainer)); ClientAirdropConfigModel = config; ClientAirdropLootResultModel = lootData; } @@ -281,5 +278,11 @@ private void SetDistanceToDrop() new Vector3(AirdropParameters.RandomAirdropPoint.x, AirdropParameters.DropHeight, AirdropParameters.RandomAirdropPoint.z), airdropPlane.transform.position); } + + protected void OnDestroy() + { + if (Singleton.Instantiated) + Singleton.Release(Singleton.Instance); + } } } \ No newline at end of file diff --git a/Source/Coop/Components/ActionPacketHandlerComponent.cs b/Source/Coop/Components/ActionPacketHandlerComponent.cs index 300058b35..c300b9be4 100644 --- a/Source/Coop/Components/ActionPacketHandlerComponent.cs +++ b/Source/Coop/Components/ActionPacketHandlerComponent.cs @@ -1,45 +1,20 @@ -using Aki.Custom.Airdrops; -using Aki.Custom.Airdrops.Models; -using BepInEx.Logging; +using BepInEx.Logging; using Comfort.Common; using EFT; -using EFT.MovingPlatforms; -using EFT.UI.BattleTimer; -using StayInTarkov.AkiSupport.Airdrops.Models; using StayInTarkov.Coop.Components.CoopGameComponents; -using StayInTarkov.Coop.Matchmaker; using StayInTarkov.Coop.NetworkPacket; -using StayInTarkov.Coop.NetworkPacket.Player; using StayInTarkov.Coop.Players; -using StayInTarkov.Coop.SITGameModes; -using StayInTarkov.Coop.World; -//using StayInTarkov.Core.Player; -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net.Sockets; -using System.Reflection; -using System.Threading.Tasks; using UnityEngine; -using UnityEngine.Profiling; namespace StayInTarkov.Coop.Components { public class ActionPacketHandlerComponent : MonoBehaviour { public readonly BlockingCollection ActionSITPackets = new(9999); - - public readonly BlockingCollection> ActionPackets = new(9999); - public BlockingCollection> ActionPacketsMovement { get; private set; } = new(9999); public ConcurrentDictionary Players => CoopGameComponent.Players; public ManualLogSource Logger { get; private set; } - private List RemovedFromAIPlayers = new(); - - private CoopSITGame CoopGame { get; } = (CoopSITGame)Singleton.Instance; - private SITGameComponent CoopGameComponent { get; set; } void Awake() @@ -48,59 +23,25 @@ void Awake() // Create a BepInEx Logger for ActionPacketHandlerComponent Logger = BepInEx.Logging.Logger.CreateLogSource("ActionPacketHandlerComponent"); Logger.LogDebug("Awake"); - - CoopGameComponent = CoopPatches.CoopGameComponentParent.GetComponent(); - ActionPacketsMovement = new(); } void Start() { - CoopGameComponent = CoopPatches.CoopGameComponentParent.GetComponent(); - ActionPacketsMovement = new(); + CoopGameComponent = this.gameObject.GetComponent(); } - //void Update() - //{ - // ProcessActionPackets(); - //} - - void LateUpdate() + void Update() { ProcessActionPackets(); } - - - - public static ActionPacketHandlerComponent GetThisComponent() - { - if (CoopPatches.CoopGameComponentParent == null) - return null; - - if (CoopPatches.CoopGameComponentParent.TryGetComponent(out var component)) - return component; - - return null; - } - private void ProcessActionPackets() { - if (CoopGameComponent == null) - { - if (CoopPatches.CoopGameComponentParent != null) - { - CoopGameComponent = CoopPatches.CoopGameComponentParent.GetComponent(); - if (CoopGameComponent == null) - return; - } - } + CoopGameComponent = gameObject.GetComponent(); if (Singleton.Instance == null) return; - if (ActionPackets == null) - return; - if (Players == null) return; @@ -127,466 +68,7 @@ private void ProcessActionPackets() #endif } - if (ActionPackets.Count > 0) - { -#if DEBUGPACKETS - Stopwatch stopwatchActionPackets = Stopwatch.StartNew(); -#endif - while (ActionPackets.TryTake(out var result)) - { -#if DEBUGPACKETS - Stopwatch stopwatchActionPacket = Stopwatch.StartNew(); -#endif - if (!ProcessLastActionDataPacket(result)) - { - //ActionPackets.Add(result); - continue; - } - -#if DEBUGPACKETS - if (stopwatchActionPacket.ElapsedMilliseconds > 1) - Logger.LogDebug($"ActionPacket {result["m"]} took {stopwatchActionPacket.ElapsedMilliseconds}ms to process!"); -#endif - } -#if DEBUGPACKETS - if (stopwatchActionPackets.ElapsedMilliseconds > 1) - Logger.LogDebug($"ActionPackets took {stopwatchActionPackets.ElapsedMilliseconds}ms to process!"); -#endif - } - - if (ActionPacketsMovement != null && ActionPacketsMovement.Count > 0) - { -#if DEBUGPACKETS - Stopwatch stopwatchActionPacketsMovement = Stopwatch.StartNew(); -#endif - while (ActionPacketsMovement.TryTake(out var result)) - { - if (!ProcessLastActionDataPacket(result)) - { - //ActionPacketsMovement.Add(result); - continue; - } - } -#if DEBUGPACKETS - if (stopwatchActionPacketsMovement.ElapsedMilliseconds > 1) - { - Logger.LogDebug($"ActionPacketsMovement took {stopwatchActionPacketsMovement.ElapsedMilliseconds}ms to process!"); - } -#endif - } - - - //if (ActionPacketsDamage != null && ActionPacketsDamage.Count > 0) - //{ - // Stopwatch stopwatchActionPacketsDamage = Stopwatch.StartNew(); - // while (ActionPacketsDamage.TryTake(out var packet)) - // { - // if (!packet.ContainsKey("profileId")) - // continue; - - // var profileId = packet["profileId"].ToString(); - - // // The person is missing. Lets add this back until they exist - // if (!CoopGameComponent.Players.ContainsKey(profileId)) - // { - // //ActionPacketsDamage.Add(packet); - // continue; - // } - - // var playerKVP = CoopGameComponent.Players[profileId]; - // if (playerKVP == null) - // continue; - - // var coopPlayer = (CoopPlayer)playerKVP; - // coopPlayer.ReceiveDamageFromServer(packet); - // } - // if (stopwatchActionPacketsDamage.ElapsedMilliseconds > 1) - // { - // Logger.LogDebug($"ActionPacketsDamage took {stopwatchActionPacketsDamage.ElapsedMilliseconds}ms to process!"); - // } - //} - - return; } - - bool ProcessSITActionPackets(ISITPacket packet) - { - //Logger.LogInfo($"{nameof(ProcessSITActionPackets)} received {packet.GetType()}"); - - packet.Process(); - - //var playerPacket = packet as BasePlayerPacket; - //if (playerPacket != null) - //{ - // if (!Players.ContainsKey(playerPacket.ProfileId)) - // return false; - - // Players[playerPacket.ProfileId].ProcessSITPacket(playerPacket); - //} - //else - //{ - // // Process Player States Packet - // if(packet is PlayerStatesPacket playerStatesPacket) - // { - // foreach(var psp in playerStatesPacket.PlayerStates) - // { - // ProcessSITActionPackets(psp); - // } - // } - //} - - return false; - } - - bool ProcessLastActionDataPacket(Dictionary packet) - { - if (Singleton.Instance == null) - return false; - - if (packet == null || packet.Count == 0) - { - Logger.LogInfo("No Data Returned from Last Actions!"); - return false; - } - - - bool result = ProcessPlayerPacket(packet); - if (!result) - result = ProcessWorldPacket(ref packet); - - return result; - } - - private bool ProcessPlayerStatesPacket(PlayerStatesPacket playerStatesPacket) - { - return false; - } - - bool ProcessWorldPacket(ref Dictionary packet) - { - // this isn't a world packet. return true - if (packet.ContainsKey("profileId")) - return true; - - // this isn't a world packet. return true - if (!packet.ContainsKey("m")) - return true; - - var result = false; - string method = packet["m"].ToString(); - - foreach (var coopPatch in CoopPatches.NoMRPPatches) - { - if (coopPatch is IModuleReplicationWorldPatch imrwp) - { - if (imrwp.MethodName == method) - { - imrwp.Replicated(ref packet); - result = true; - } - } - } - - switch (method) - { - case "AirdropPacket": - ReplicateAirdrop(packet); - result = true; - break; - case "AirdropLootPacket": - ReplicateAirdropLoot(packet); - result = true; - break; - //case "RaidTimer": - // ReplicateRaidTimer(packet); - // result = true; - // break; - case "TimeAndWeather": - ReplicateTimeAndWeather(packet); - result = true; - break; - case "ArmoredTrainTime": - ReplicateArmoredTrainTime(packet); - result = true; - break; - case "LootableContainer_Interact": - LootableContainer_Interact_Patch.Replicated(packet); - result = true; - break; - } - - return result; - } - - bool ProcessPlayerPacket(Dictionary packet) - { - - if (packet == null) - return true; - - var profileId = ""; - - if (packet.ContainsKey("profileId")) - profileId = packet["profileId"].ToString(); - - if (packet.ContainsKey("ProfileId")) - profileId = packet["ProfileId"].ToString(); - - if (string.IsNullOrEmpty(profileId)) - return false; - - if (Players == null) - { - Logger.LogDebug("Players is Null"); - return false; - } - - if (Players.Count == 0) - { - Logger.LogDebug("Players is Empty"); - return false; - } - - if (!Players.ContainsKey(profileId)) - return false; - - var plyr = Players[profileId]; - if(plyr == null) - return false; - - plyr.ProcessModuleReplicationPatch(packet); - - return true; - } - - async Task WaitForPlayerAndProcessPacket(string profileId, Dictionary packet) - { - // Start the timer. - var startTime = DateTime.Now; - var maxWaitTime = TimeSpan.FromMinutes(2); - - while (true) - { - // Check if maximum wait time has been reached. - if (DateTime.Now - startTime > maxWaitTime) - { - Logger.LogError($"{DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss.fff")}: WaitForPlayerAndProcessPacket waited for {maxWaitTime.TotalMinutes} minutes, but player {profileId} still did not exist after timeout period."); - return; - } - - if (Players == null) - continue; - - var registeredPlayers = Singleton.Instance.RegisteredPlayers; - - // If the player now exists, process the packet and end the thread. - if (Players.Any(x => x.Key == profileId) || registeredPlayers.Any(x => x.Profile.ProfileId == profileId)) - { - // Logger.LogDebug($"{DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss.fff")}: WaitForPlayerAndProcessPacket waited for {(DateTime.Now - startTime).TotalSeconds}s"); - ProcessPlayerPacket(packet); - return; - } - - // Wait for a short period before checking again. - await Task.Delay(1000); - } - } - - void ReplicateAirdrop(Dictionary packet) - { - if (!Singleton.Instantiated) - return; - - Logger.LogInfo("--- RAW AIRDROP PACKET ---"); - Logger.LogInfo(packet.SITToJson()); - - Singleton.Instance.AirdropParameters = packet["model"].ToString().SITParseJson(); - } - - void ReplicateAirdropLoot(Dictionary packet) - { - if (!Singleton.Instantiated) - return; - - Logger.LogInfo("--- RAW AIRDROP-LOOT PACKET ---"); - Logger.LogInfo(packet.SITToJson()); - - Singleton.Instance.ReceiveBuildLootContainer( - packet["result"].ToString().SITParseJson(), - packet["config"].ToString().SITParseJson()); - } - - //void ReplicateRaidTimer(Dictionary packet) - //{ - // SITGameComponent coopGameComponent = SITGameComponent.GetCoopGameComponent(); - // if (coopGameComponent == null) - // return; - - // if (SITMatchmaking.IsClient) - // { - // var sessionTime = new TimeSpan(long.Parse(packet["sessionTime"].ToString())); - // Logger.LogDebug($"RaidTimer: Remaining session time {sessionTime.TraderFormat()}"); - - // if (coopGameComponent.LocalGameInstance is CoopSITGame coopGame) - // { - // var gameTimer = coopGame.GameTimer; - // if (gameTimer.StartDateTime.HasValue && gameTimer.SessionTime.HasValue) - // { - // if (gameTimer.PastTime.TotalSeconds < 3) - // return; - - // var timeRemain = gameTimer.PastTime + sessionTime; - - // if (Math.Abs(gameTimer.SessionTime.Value.TotalSeconds - timeRemain.TotalSeconds) < 5) - // return; - - // Logger.LogInfo($"RaidTimer: New SessionTime {timeRemain.TraderFormat()}"); - // gameTimer.ChangeSessionTime(timeRemain); - - // MainTimerPanel mainTimerPanel = ReflectionHelpers.GetFieldOrPropertyFromInstance(coopGame.GameUi.TimerPanel, "_mainTimerPanel", false); - // if (mainTimerPanel != null) - // { - // FieldInfo extractionDateTimeField = ReflectionHelpers.GetFieldFromType(typeof(TimerPanel), "dateTime_0"); - // extractionDateTimeField.SetValue(mainTimerPanel, gameTimer.StartDateTime.Value.AddSeconds(timeRemain.TotalSeconds)); - - // MethodInfo UpdateTimerMI = ReflectionHelpers.GetMethodForType(typeof(MainTimerPanel), "UpdateTimer"); - // UpdateTimerMI.Invoke(mainTimerPanel, new object[] { }); - // } - // } - // } - // } - //} - - void ReplicateTimeAndWeather(Dictionary packet) - { - SITGameComponent coopGameComponent = SITGameComponent.GetCoopGameComponent(); - if (coopGameComponent == null) - return; - - if (SITMatchmaking.IsClient) - { - Logger.LogDebug(packet.ToJson()); - - var gameDateTime = new DateTime(long.Parse(packet["GameDateTime"].ToString())); - if (coopGameComponent.LocalGameInstance is CoopSITGame coopGame && coopGame.GameDateTime != null) - coopGame.GameDateTime.Reset(gameDateTime); - - var weatherController = EFT.Weather.WeatherController.Instance; - if (weatherController != null) - { - var weatherDebug = weatherController.WeatherDebug; - if (weatherDebug != null) - { - weatherDebug.Enabled = true; - - weatherDebug.CloudDensity = float.Parse(packet["CloudDensity"].ToString()); - weatherDebug.Fog = float.Parse(packet["Fog"].ToString()); - weatherDebug.LightningThunderProbability = float.Parse(packet["LightningThunderProbability"].ToString()); - weatherDebug.Rain = float.Parse(packet["Rain"].ToString()); - weatherDebug.Temperature = float.Parse(packet["Temperature"].ToString()); - weatherDebug.TopWindDirection = new(float.Parse(packet["TopWindDirection.x"].ToString()), float.Parse(packet["TopWindDirection.y"].ToString())); - - Vector2 windDirection = new(float.Parse(packet["WindDirection.x"].ToString()), float.Parse(packet["WindDirection.y"].ToString())); - - // working dog sh*t, if you are the programmer, DON'T EVER DO THIS! - dounai2333 - static bool BothPositive(float f1, float f2) => f1 > 0 && f2 > 0; - static bool BothNegative(float f1, float f2) => f1 < 0 && f2 < 0; - static bool VectorIsSameQuadrant(Vector2 v1, Vector2 v2, out int flag) - { - flag = 0; - if (v1.x != 0 && v1.y != 0 && v2.x != 0 && v2.y != 0) - { - if ((BothPositive(v1.x, v2.x) && BothPositive(v1.y, v2.y)) - || (BothNegative(v1.x, v2.x) && BothNegative(v1.y, v2.y)) - || (BothPositive(v1.x, v2.x) && BothNegative(v1.y, v2.y)) - || (BothNegative(v1.x, v2.x) && BothPositive(v1.y, v2.y))) - { - flag = 1; - return true; - } - } - else - { - if (v1.x != 0 && v2.x != 0) - { - if (BothPositive(v1.x, v2.x) || BothNegative(v1.x, v2.x)) - { - flag = 1; - return true; - } - } - else if (v1.y != 0 && v2.y != 0) - { - if (BothPositive(v1.y, v2.y) || BothNegative(v1.y, v2.y)) - { - flag = 2; - return true; - } - } - } - return false; - } - - for (int i = 1; i < WeatherClass.WindDirections.Count(); i++) - { - Vector2 direction = WeatherClass.WindDirections[i]; - if (VectorIsSameQuadrant(windDirection, direction, out int flag)) - { - weatherDebug.WindDirection = (EFT.Weather.WeatherDebug.Direction)i; - weatherDebug.WindMagnitude = flag switch - { - 1 => windDirection.x / direction.x, - 2 => windDirection.y / direction.y, - _ => weatherDebug.WindMagnitude - }; - break; - } - } - } - else - { - Logger.LogError("TimeAndWeather: WeatherDebug is null!"); - } - } - else - { - Logger.LogError("TimeAndWeather: WeatherController is null!"); - } - } - } - - void ReplicateArmoredTrainTime(Dictionary packet) - { - SITGameComponent coopGameComponent = SITGameComponent.GetCoopGameComponent(); - if (coopGameComponent == null) - return; - - if (SITMatchmaking.IsClient) - { - DateTime utcTime = new(long.Parse(packet["utcTime"].ToString())); - - if (coopGameComponent.LocalGameInstance is CoopSITGame coopGame) - { - Timer1 gameTimer = coopGame.GameTimer; - - // Process only after raid began. - if (gameTimer.StartDateTime.HasValue && gameTimer.SessionTime.HasValue) - { - // Looking for Armored Train, if there is nothing, then we are not on the Reserve or Lighthouse. - Locomotive locomotive = FindObjectOfType(); - if (locomotive != null) - { - // The time won't change, if we already have replicated the time, don't override it again. - FieldInfo departField = ReflectionHelpers.GetFieldFromType(typeof(MovingPlatform), "_depart"); - if (utcTime == (DateTime)departField.GetValue(locomotive)) - return; - - locomotive.Init(utcTime); - } - } - } - } - } } } diff --git a/Source/Coop/Components/CoopGameComponents/SITGameComponent.cs b/Source/Coop/Components/CoopGameComponents/SITGameComponent.cs index 2ba41010f..866b3fd07 100644 --- a/Source/Coop/Components/CoopGameComponents/SITGameComponent.cs +++ b/Source/Coop/Components/CoopGameComponents/SITGameComponent.cs @@ -1,42 +1,37 @@ using Comfort.Common; using EFT; using EFT.Communications; +using EFT.Counters; using EFT.Interactive; using EFT.InventoryLogic; using EFT.UI; +using Google.FlatBuffers; using Newtonsoft.Json.Linq; +using StayInTarkov.AkiSupport.Singleplayer.Utils.InRaid; using StayInTarkov.Configuration; using StayInTarkov.Coop.Components; using StayInTarkov.Coop.Controllers.Health; using StayInTarkov.Coop.Matchmaker; using StayInTarkov.Coop.NetworkPacket.Player; using StayInTarkov.Coop.NetworkPacket.Player.Health; +using StayInTarkov.Coop.NetworkPacket.World; using StayInTarkov.Coop.Player; using StayInTarkov.Coop.Players; using StayInTarkov.Coop.SITGameModes; using StayInTarkov.Coop.Web; -//using StayInTarkov.Core.Player; -using StayInTarkov.EssentialPatches; -using StayInTarkov.Memory; +using StayInTarkov.FlatBuffers; using StayInTarkov.Networking; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Text; -using System.Threading.Tasks; +using Systems.Effects; using UnityEngine; - +using FBPacket = StayInTarkov.FlatBuffers.Packet; using Rect = UnityEngine.Rect; -using StayInTarkov.Coop.NetworkPacket.Raid; -using Diz.Jobs; -using System.Net.NetworkInformation; -using EFT.Counters; -using StayInTarkov.AkiSupport.Singleplayer.Utils.InRaid; -using StayInTarkov.Coop.NetworkPacket.World; namespace StayInTarkov.Coop.Components.CoopGameComponents { @@ -46,7 +41,7 @@ namespace StayInTarkov.Coop.Components.CoopGameComponents public class SITGameComponent : MonoBehaviour { #region Fields/Properties - public WorldInteractiveObject[] ListOfInteractiveObjects { get; set; } + public ConcurrentDictionary WorldnteractiveObjects { get; set; } = []; private AkiBackendCommunication RequestingObj { get; set; } public SITConfig SITConfig { get; private set; } = new SITConfig(); public string ServerId { get; set; } = null; @@ -63,7 +58,6 @@ public class SITGameComponent : MonoBehaviour /// public HashSet PlayerClients { get; } = new(); - //public EFT.Player[] PlayerUsers public IEnumerable PlayerUsers { get @@ -102,9 +96,9 @@ public EFT.Player[] PlayerBots } /// - /// This is all the spawned players via the spawning process. Not anyone else. + /// This is all the spawned characters created via the spawning process. Not anyone else. /// - public Dictionary SpawnedPlayers { get; private set; } = new(); + public Dictionary SpawnedCharacters { get; private set; } = new(); public BepInEx.Logging.ManualLogSource Logger { get; private set; } public ConcurrentDictionary PlayersToSpawn { get; private set; } = new(); @@ -118,10 +112,6 @@ public EFT.Player[] PlayerBots public List SpawnedPlayersToFinalize { get; private set; } = new(); - public BlockingCollection> ActionPackets => ActionPacketHandler.ActionPackets; - - private Dictionary[] m_CharactersJson { get; set; } - public bool RunAsyncTasks { get; set; } = true; float screenScale = 1.0f; @@ -130,23 +120,21 @@ public EFT.Player[] PlayerBots public ActionPacketHandlerComponent ActionPacketHandler { get; set; } + public static SITGameComponent Instance { get; set; } + #endregion #region Public Voids public static SITGameComponent GetCoopGameComponent() { - if (CoopPatches.CoopGameComponentParent == null) - { - StayInTarkovHelperConstants.Logger.LogError($"Attempted to use {nameof(GetCoopGameComponent)} before {nameof(SITGameComponent)} has been created."); - return null; - } - - var coopGameComponent = CoopPatches.CoopGameComponentParent.GetComponent(); - if (coopGameComponent != null) - return coopGameComponent; - + if (Instance != null) + return Instance; +#if DEBUG StayInTarkovHelperConstants.Logger.LogError($"Attempted to use {nameof(GetCoopGameComponent)} before {nameof(SITGameComponent)} has been created."); + StayInTarkovHelperConstants.Logger.LogError(new System.Diagnostics.StackTrace()); +#endif + return null; } @@ -158,14 +146,7 @@ public static bool TryGetCoopGameComponent(out SITGameComponent coopGameComponen public static string GetServerId() { - var coopGC = GetCoopGameComponent(); - if (coopGC == null) - { - StayInTarkovHelperConstants.Logger.LogError($"Attempted to use {nameof(GetServerId)} before {nameof(SITGameComponent)} has been created."); - return null; - } - - return coopGC.ServerId; + return SITMatchmaking.GetGroupId(); } #endregion @@ -181,7 +162,7 @@ void Awake() Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(SITGameComponent)); Logger.LogDebug($"{nameof(SITGameComponent)}:{nameof(Awake)}"); - ActionPacketHandler = CoopPatches.CoopGameComponentParent.GetOrAddComponent(); + ActionPacketHandler = gameObject.GetOrAddComponent(); gameObject.AddComponent(); SITCheck(); @@ -207,13 +188,7 @@ void SITCheck() /// async void Start() { - Logger.LogDebug("CoopGameComponent:Start"); - - // Get Reference to own Player - //OwnPlayer = (LocalPlayer)Singleton.Instance.MainPlayer; - - //// Add own Player to Players list - //Players.TryAdd(OwnPlayer.ProfileId, (CoopPlayer)OwnPlayer); + Instance = this; // Instantiate the Requesting Object for Aki Communication RequestingObj = AkiBackendCommunication.GetRequestInstance(false, Logger); @@ -241,7 +216,7 @@ await RequestingObj.PostJsonAsync("/SIT/Config", "{}").ContinueWith(x // Enable the Coop Patches CoopPatches.EnableDisablePatches(); - Singleton.Instance.AfterGameStarted += GameWorld_AfterGameStarted;; + Singleton.Instance.AfterGameStarted += GameWorld_AfterGameStarted; ; // In game ping system. if (Singleton.Instantiated) @@ -257,42 +232,94 @@ await RequestingObj.PostJsonAsync("/SIT/Config", "{}").ContinueWith(x private IEnumerator SendPlayerStatePacket() { - using PlayerStatesPacket playerStatesPacket = new PlayerStatesPacket(); - - List packets = new List(); - //foreach (var player in Players.Values) - foreach (var player in Singleton.Instance.AllAlivePlayersList) + var players = Singleton.Instance.AllAlivePlayersList; + foreach (var player in players) { - //if (!GameWorldGameStarted) - // continue; - if (player == null) continue; if (player is CoopPlayerClient) continue; - //if (!player.TryGetComponent(out PlayerReplicatedComponent prc)) - // continue; - - //if (prc.IsClientDrone) - // continue; - if (!player.enabled) continue; if (!player.isActiveAndEnabled) continue; - CreatePlayerStatePacketFromPRC(ref packets, player); - } + var builder = new FlatBufferBuilder(1024); + + var profileId = builder.CreateString(player.ProfileId); - //playerStates.Add("dataList", playerStateArray); - //Logger.LogDebug(playerStates.SITToJson()); - playerStatesPacket.PlayerStates = packets.ToArray(); - var serializedPlayerStates = playerStatesPacket.Serialize(); + PlayerState.StartPlayerState(builder); - //Logger.LogDebug($"{nameof(playerStatesPacket)} is {serialized.Length} in Length"); + // Iterate over the BodyParts + { + // no Span, Sadge + var currents = new float[Enum.GetValues(typeof(EBodyPart)).Length]; + var maximums = new float[Enum.GetValues(typeof(EBodyPart)).Length]; + foreach (EBodyPart bodyPart in Enum.GetValues(typeof(EBodyPart))) + { + var health = player.HealthController.GetBodyPartHealth(bodyPart); + currents[(byte)bodyPart] = health.Current; + maximums[(byte)bodyPart] = health.Maximum; + } + PlayerState.AddBodyPartsHealth(builder, BodyPartsHealth.CreateBodyPartsHealth(builder, currents, maximums)); + } + + // TODO(belette) add this later? seems unused today since these packets only carry profileId and no relevant info atm + //if (player.HealthController is SITHealthController sitHealthController) + //{ + // var tmpHealthEffectPacketList = new List(); + // while (sitHealthController.PlayerHealthEffectPackets.TryDequeue(out var p)) + // { + // tmpHealthEffectPacketList.Add(p); + // } + // playerHealth.HealthEffectPackets = tmpHealthEffectPacketList.ToArray(); + //} + + PlayerState.AddProfileId(builder, profileId); + PlayerState.AddIsAlive(builder, player.HealthController.IsAlive); + PlayerState.AddEnergy(builder, player.HealthController.Energy.Current); + PlayerState.AddHydration(builder, player.HealthController.Hydration.Current); + PlayerState.AddPosition(builder, Vec3.CreateVec3(builder, player.Position.x, player.Position.y, player.Position.z)); + PlayerState.AddRotation(builder, Vec2.CreateVec2(builder, player.Rotation.x, player.Rotation.y)); + PlayerState.AddHeadRotation(builder, Vec3.CreateVec3(builder, player.HeadRotation.x, player.HeadRotation.y, player.HeadRotation.z)); + PlayerState.AddMovementDirection(builder, Vec2.CreateVec2(builder, player.MovementContext.MovementDirection.x, player.MovementContext.MovementDirection.y)); + PlayerState.AddState(builder, (FlatBuffers.EPlayerState)player.MovementContext.CurrentState.Name); + PlayerState.AddTilt(builder, player.MovementContext.Tilt); + PlayerState.AddStep(builder, (sbyte)player.MovementContext.Step); + PlayerState.AddAnimatorStateIndex(builder, (byte)player.MovementContext.CurrentAnimatorStateIndex); + PlayerState.AddCharacterMovementSpeed(builder, player.MovementContext.CharacterMovementSpeed); + PlayerState.AddIsProne(builder, player.MovementContext.IsInPronePose); + PlayerState.AddPoseLevel(builder, player.MovementContext.PoseLevel); + PlayerState.AddIsSprinting(builder, player.MovementContext.IsSprintEnabled); + PlayerState.AddInputDirection(builder, Vec2.CreateVec2(builder, player.InputDirection.x, player.InputDirection.y)); + PlayerState.AddLeftStance(builder, player.MovementContext.LeftStanceController.LastAnimValue); + PlayerState.AddHandsExhausted(builder, player.Physical.SerializationStruct.HandsExhausted); + PlayerState.AddStaminaExhausted(builder, player.Physical.SerializationStruct.StaminaExhausted); + PlayerState.AddOxygenExhausted(builder, player.Physical.SerializationStruct.OxygenExhausted); + PlayerState.AddBlindfire(builder, (sbyte)player.MovementContext.BlindFire); + PlayerState.AddLinearSpeed(builder, player.MovementContext.ActualLinearVelocity); + var pstateOffset = PlayerState.EndPlayerState(builder); + + FBPacket.StartPacket(builder); + FBPacket.AddPacketType(builder, AnyPacket.player_state); + FBPacket.AddPacket(builder, pstateOffset.Value); + var packetOffset = FBPacket.EndPacket(builder); + + builder.Finish(packetOffset.Value); + + if (Singleton.Instance.GameClient is GameClientUDP udp) + { + var seg = builder.DataBuffer.ToArraySegment(builder.DataBuffer.Position, builder.DataBuffer.Length - builder.DataBuffer.Position); + udp.SendData(seg.Array, seg.Offset, seg.Count, SITGameServerClientDataProcessing.FLATBUFFER_CHANNEL_NUM, LiteNetLib.DeliveryMethod.Sequenced); + } + else if (Singleton.Instance.GameClient is GameClientTCPRelay) + { + GameClient.SendData(builder.SizedByteArray()); + } + } // ---------------------------------------------------------------------------------------------------- // Paulov: Keeping this here as a note. DO NOT DELETE. @@ -313,10 +340,6 @@ private IEnumerator SendPlayerStatePacket() // } // ---------------------------------------------------------------------------------------------------- - GameClient.SendData(serializedPlayerStates); - - LastPlayerStateSent = DateTime.Now; - yield return new WaitForSeconds(PluginConfigSettings.Instance.CoopSettings.SETTING_PlayerStateTickRateInMS / 1000f); StartCoroutine(SendPlayerStatePacket()); } @@ -325,14 +348,15 @@ private void GameWorld_AfterGameStarted() { GameWorldGameStarted = true; Logger.LogDebug(nameof(GameWorld_AfterGameStarted)); - //if (Singleton.Instance.RegisteredPlayers.Any()) - //{ - // // Send My Player to Aki, so that other clients know about me - // CoopSITGame.SendPlayerDataToServer((LocalPlayer)Singleton.Instance.RegisteredPlayers.First(x => x.IsYourPlayer)); - //} - GarbageCollect(); - StartCoroutine(GarbageCollectSIT()); + this.GetOrAddComponent(); + this.GetOrAddComponent(); + + // Add Server Authority components + if (SITMatchmaking.IsServer) + { + this.GetOrAddComponent(); + } StartCoroutine(SendPlayerStatePacket()); @@ -355,62 +379,14 @@ private void GameWorld_AfterGameStarted() } // Get a List of Interactive Objects (this is a slow method), so run once here to maintain a reference - ListOfInteractiveObjects = FindObjectsOfType(); - } - - private void GarbageCollect() - { - // Start the SIT Garbage Collector - Logger.LogDebug($"{nameof(GarbageCollect)}"); - BSGMemoryGC.RunHeapPreAllocation(); - BSGMemoryGC.Collect(force: true); - BSGMemoryGC.EmptyWorkingSet(); - BSGMemoryGC.GCEnabled = true; - //Resources.UnloadUnusedAssets(); - } - - /// - /// Runs the Garbage Collection every 5 minutes - /// - /// - private IEnumerator GarbageCollectSIT() - { - while(true) + foreach (WorldInteractiveObject worldInteractiveObject in FindObjectsOfType()) { - if (PluginConfigSettings.Instance.AdvancedSettings.SETTING_EnableSITGC) - { - var nearestEnemyDist = float.MaxValue; - foreach(var p in Players) - { - if (p.Key == Singleton.Instance.MainPlayer.ProfileId) - continue; - - var dist = Vector3.Distance(p.Value.Transform.position, Singleton.Instance.MainPlayer.Transform.position); - if(dist < nearestEnemyDist) - nearestEnemyDist = dist; - } - - if (nearestEnemyDist > 10) - { - var mem = MemoryInfo.GetCurrentStatus(); - if (mem == null) - { - yield return new WaitForSeconds(1); - continue; - } - - var memPercentInUse = mem.dwMemoryLoad; - Logger.LogDebug($"Total memory used: {mem.dwMemoryLoad}%"); - if (memPercentInUse > PluginConfigSettings.Instance.AdvancedSettings.SETTING_SITGCMemoryThreshold) - GarbageCollect(); - - } - } - - yield return new WaitForSeconds(60); + this.WorldnteractiveObjects.TryAdd(worldInteractiveObject.Id, worldInteractiveObject); } } + + /// /// This is a simple coroutine to allow methods to run every second. /// @@ -419,168 +395,85 @@ private IEnumerator EverySecondCoroutine() { var waitSeconds = new WaitForSeconds(1.0f); - while (RunAsyncTasks) + if (!Singleton.Instantiated) { yield return waitSeconds; + StartCoroutine(EverySecondCoroutine()); + yield break; + } - try - { - if (!Singleton.Instantiated) - continue; - - if (!Singleton.Instantiated) - continue; - - if (!GameWorldGameStarted) - continue; - - //Logger.LogDebug($"DEBUG: {nameof(EverySecondCoroutine)}"); - - var coopGame = Singleton.Instance; - - var playersToExtract = new HashSet(); - foreach (var exfilPlayer in coopGame.ExtractingPlayers) - { - var exfilTime = new TimeSpan(0, 0, (int)exfilPlayer.Value.Item1); - var timeInExfil = new TimeSpan(DateTime.Now.Ticks - exfilPlayer.Value.Item2); - if (timeInExfil >= exfilTime) - { - if (!playersToExtract.Contains(exfilPlayer.Key)) - { - Logger.LogDebug(exfilPlayer.Key + " should extract"); - playersToExtract.Add(exfilPlayer.Key); - } - } - else - { - Logger.LogDebug(exfilPlayer.Key + " extracting " + timeInExfil); - - } - } - - // Trigger all countdown exfils (e.g. car), clients are responsible for their own extract - // since exfilpoint.Entered is local because of collision logic being local - // we start from the end because we remove as we go in `CoopSITGame.ExfiltrationPoint_OnStatusChanged` - for (int i = coopGame.EnabledCountdownExfils.Count - 1; i >= 0; i--) - { - var ep = coopGame.EnabledCountdownExfils[i]; - if (coopGame.PastTime - ep.ExfiltrationStartTime >= ep.Settings.ExfiltrationTime) - { - var game = Singleton.Instance; - foreach (var player in ep.Entered) - { - var hasUnmetRequirements = ep.UnmetRequirements(player).Any(); - if (player != null && player.HealthController.IsAlive && !hasUnmetRequirements) - { - game.ExtractingPlayers.Remove(player.ProfileId); - game.ExtractedPlayers.Add(player.ProfileId); - } - } - ep.SetStatusLogged(ep.Reusable ? EExfiltrationStatus.UncompleteRequirements : EExfiltrationStatus.NotPresent, nameof(EverySecondCoroutine)); - } - } - - foreach (var player in playersToExtract) - { - coopGame.ExtractingPlayers.Remove(player); - coopGame.ExtractedPlayers.Add(player); - } - - var world = Singleton.Instance; - - // Hide extracted Players - foreach (var profileId in coopGame.ExtractedPlayers) - { - var player = world.RegisteredPlayers.Find(x => x.ProfileId == profileId) as EFT.Player; - if (player == null) - continue; - - if (!ExtractedProfilesSent.Contains(profileId)) - { - ExtractedProfilesSent.Add(profileId); - if (player.Profile.Side == EPlayerSide.Savage) - { - player.Profile.EftStats.SessionCounters.AddDouble(0.01, - [ - CounterTag.FenceStanding, - EFenceStandingSource.ExitStanding - ]); - } - AkiBackendCommunicationCoop.PostLocalPlayerData(player - , new Dictionary() { { "m", "Extraction" }, { "Extracted", true } } - ); - } - - if (player.ActiveHealthController != null) - { - if (!player.ActiveHealthController.MetabolismDisabled) - { - player.ActiveHealthController.AddDamageMultiplier(0); - player.ActiveHealthController.SetDamageCoeff(0); - player.ActiveHealthController.DisableMetabolism(); - player.ActiveHealthController.PauseAllEffects(); - - //player.SwitchRenderer(false); - - // TODO: Currently. Destroying your own Player just breaks the game and it appears to be "frozen". Need to learn a new way to do a FreeCam! - //if (Singleton.Instance.MainPlayer.ProfileId != profileId) - // Destroy(player); - } - } - //force close all screens to disallow looting open crates after extract - if (profileId == world.MainPlayer.ProfileId) - { - ScreenManager instance = ScreenManager.Instance; - instance.CloseAllScreensForced(); - } - - PlayerUtils.MakeVisible(player, false); - } + if (!Singleton.Instantiated) + { + yield return waitSeconds; + StartCoroutine(EverySecondCoroutine()); + yield break; + } - // Add players who have joined to the AI Enemy Lists - var botController = (BotsController)ReflectionHelpers.GetFieldFromTypeByFieldType(typeof(BaseLocalGame), typeof(BotsController)).GetValue(Singleton.Instance); - if (botController != null) - { - while (PlayersForAIToTarget.TryDequeue(out var otherPlayer)) - { - Logger.LogDebug($"Adding {otherPlayer.Profile.Nickname} to Enemy list"); - botController.AddActivePLayer(otherPlayer); - botController.AddEnemyToAllGroups(otherPlayer, otherPlayer, otherPlayer); - } - } + if (!GameWorldGameStarted) + { + yield return waitSeconds; + StartCoroutine(EverySecondCoroutine()); + yield break; + } - if (Singleton.Instance.GameClient is GameClientUDP udp) - { - coopGame.GameClient.ResetStats(); - } + var coopGame = Singleton.Instance; - ProcessOtherModsSpawnedPlayers(); + var world = Singleton.Instance; - } - catch (Exception ex) + // Add players who have joined to the AI Enemy Lists + var botController = (BotsController)ReflectionHelpers.GetFieldFromTypeByFieldType(typeof(BaseLocalGame), typeof(BotsController)).GetValue(Singleton.Instance); + if (botController != null) + { + while (PlayersForAIToTarget.TryDequeue(out var otherPlayer)) { - Logger.LogError($"{nameof(EverySecondCoroutine)}: caught exception:\n{ex}"); + Logger.LogDebug($"Adding {otherPlayer.Profile.Nickname} to Enemy list"); + botController.AddActivePLayer(otherPlayer); + botController.AddEnemyToAllGroups(otherPlayer, otherPlayer, otherPlayer); } } - } - private void ProcessOtherModsSpawnedPlayers() - { - // If another mod has spawned people, attempt to handle it. - foreach (var p in Singleton.Instance.AllAlivePlayersList) + if (Singleton.Instance.GameClient is GameClientUDP udp) + { + coopGame.GameClient.ResetStats(); + } + + // TODO: Make this work. + //ProcessOtherModsSpawnedPlayers(); + + if (DespawnList.Count > 0) { - if (!Players.ContainsKey(p.ProfileId)) + foreach (var despawnId in DespawnList) { - // As these created players are unlikely to be CoopPlayer, throw an error! - if((p as CoopPlayer) == null) - { - Logger.LogError($"Player of Id:{p.ProfileId} is not found in the SIT {nameof(Players)} list?!"); - } + Logger.LogInfo($"Attempting to despawn queued character: {despawnId}"); + + if (DespawnCharacter(despawnId)) + DespawnList.Remove(despawnId); } - } + }; + + yield return waitSeconds; + + //Logger.LogInfo($"{nameof(EverySecondCoroutine)}"); + StartCoroutine(EverySecondCoroutine()); + } - private HashSet ExtractedProfilesSent = new(); + // TODO: Make this work. + //private void ProcessOtherModsSpawnedPlayers() + //{ + // // If another mod has spawned people, attempt to handle it. + // foreach (var p in Singleton.Instance.AllAlivePlayersList) + // { + // if (!Players.ContainsKey(p.ProfileId)) + // { + // // As these created players are unlikely to be CoopPlayer, throw an error! + // if ((p as CoopPlayer) == null) + // { + // Logger.LogError($"Player of Id:{p.ProfileId} is not found in the SIT {nameof(Players)} list?!"); + // } + // } + // } + //} void OnDestroy() { @@ -590,16 +483,16 @@ void OnDestroy() PlayersToSpawnProfiles.Clear(); PlayersToSpawnPositions.Clear(); PlayersToSpawnPacket.Clear(); + DespawnList.Clear(); RunAsyncTasks = false; - //StopCoroutine(ProcessServerCharacters()); StopCoroutine(EverySecondCoroutine()); CoopPatches.EnableDisablePatches(); + GameObject.Destroy(this.GetComponent()); + GameObject.Destroy(this.GetComponent()); + Instance = null; } - TimeSpan LateUpdateSpan = TimeSpan.Zero; - Stopwatch swActionPackets { get; } = new Stopwatch(); - bool PerformanceCheck_ActionPackets { get; set; } = false; public bool RequestQuitGame { get; set; } int ForceQuitGamePressed = 0; @@ -695,7 +588,7 @@ void ProcessQuitting() var quitState = GetQuitState(); if ( - Input.GetKeyDown(KeyCode.F8) + PluginConfigSettings.Instance.CoopSettings.SETTING_PressToExtractKey.Value.IsDown() && quitState != EQuitState.NONE && !RequestQuitGame @@ -725,7 +618,7 @@ void ProcessQuitting() return; } else if ( - Input.GetKeyDown(KeyCode.F7) + PluginConfigSettings.Instance.CoopSettings.SETTING_PressToForceExtractKey.Value.IsDown() && quitState != EQuitState.NONE && !RequestQuitGame @@ -801,8 +694,8 @@ private void FindALocationAndProcessQuit() if (Singleton.Instance.MyExitLocation == null) { var gameWorld = Singleton.Instance; - List scavExfilFiltered = new List(); - List pmcExfilPiltered = new List(); + List scavExfilFiltered = new(); + List pmcExfilPiltered = new(); foreach (var exfil in gameWorld.ExfiltrationController.ExfiltrationPoints) { if (exfil is ScavExfiltrationPoint scavExfil) @@ -875,8 +768,8 @@ void ProcessServerHasStopped() if (Singleton.Instance.MyExitLocation == null) { var gameWorld = Singleton.Instance; - List scavExfilFiltered = new List(); - List pmcExfilPiltered = new List(); + List scavExfilFiltered = new(); + List pmcExfilPiltered = new(); foreach (var exfil in gameWorld.ExfiltrationController.ExfiltrationPoints) { if (exfil is ScavExfiltrationPoint scavExfil) @@ -951,9 +844,6 @@ void Update() ProcessServerHasStopped(); ProcessServerCharacters(); - if (ActionPackets == null) - return; - if (Players == null) return; @@ -963,10 +853,6 @@ void Update() if (RequestingObj == null) return; - - - - if (SpawnedPlayersToFinalize == null) return; @@ -1006,142 +892,6 @@ void Update() #endregion - //private async Task ReadFromServerCharactersLoop() - //{ - // if (GetServerId() == null) - // return; - - - // while (RunAsyncTasks) - // { - // await Task.Delay(10000); - - // if (Players == null) - // continue; - - // await ReadFromServerCharacters(); - - // } - //} - - //private async Task ReadFromServerCharacters() - //{ - // // ----------------------------------------------------------------------------------------------------------- - // // We must filter out characters that already exist on this match! - // // - // var playerList = new List(); - // if (!PluginConfigSettings.Instance.CoopSettings.SETTING_DEBUGSpawnDronesOnServer) - // { - // if (PlayersToSpawn.Count > 0) - // playerList.AddRange(PlayersToSpawn.Keys.ToArray()); - // if (Players.Keys.Any()) - // playerList.AddRange(Players.Keys.ToArray()); - // if (Singleton.Instance.RegisteredPlayers.Any()) - // playerList.AddRange(Singleton.Instance.RegisteredPlayers.Select(x => x.ProfileId)); - // if (Singleton.Instance.AllAlivePlayersList.Count > 0) - // playerList.AddRange(Singleton.Instance.AllAlivePlayersList.Select(x => x.ProfileId)); - // } - // // - // // ----------------------------------------------------------------------------------------------------------- - // await Task.Run(() => - // { - // // Ensure this is a distinct list of Ids - // var distinctExistingProfileIds = playerList.Distinct().ToArray(); - // RequestSpawnPlayersPacket requestSpawnPlayersPacket = new RequestSpawnPlayersPacket(distinctExistingProfileIds); - // GameClient.SendData(requestSpawnPlayersPacket.Serialize()); - // }); - // //try - // //{ - // // m_CharactersJson = await RequestingObj.PostJsonAsync[]>("/coop/server/read/players", jsonDataToSend, 30000); - // // if (m_CharactersJson == null) - // // return; - - // // if (!m_CharactersJson.Any()) - // // return; - - // // if (m_CharactersJson[0].ContainsKey("notFound")) - // // { - // // // Game is broken and doesn't exist! - // // if (LocalGameInstance != null) - // // { - // // ServerHasStopped = true; - // // } - // // return; - // // } - - // // //Logger.LogDebug($"CoopGameComponent.ReadFromServerCharacters:{actionsToValues.Length}"); - - // // var packets = m_CharactersJson - // // .Where(x => x != null); - // // if (packets == null) - // // return; - - // // foreach (var queuedPacket in packets) - // // { - // // if (queuedPacket != null && queuedPacket.Count > 0) - // // { - // // if (queuedPacket != null) - // // { - // // if (queuedPacket.ContainsKey("m")) - // // { - // // var method = queuedPacket["m"].ToString(); - // // if (method != "PlayerSpawn") - // // continue; - - // // string profileId = queuedPacket["profileId"].ToString(); - // // if (!PluginConfigSettings.Instance.CoopSettings.SETTING_DEBUGSpawnDronesOnServer) - // // { - // // if (Players == null - // // || Players.ContainsKey(profileId) - // // || Singleton.Instance.RegisteredPlayers.Any(x => x.ProfileId == profileId) - // // ) - // // { - // // Logger.LogDebug($"Ignoring call to Spawn player {profileId}. The player already exists in the game."); - // // continue; - // // } - // // } - - // // if (PlayersToSpawn.ContainsKey(profileId)) - // // continue; - - // // if (!PlayersToSpawnPacket.ContainsKey(profileId)) - // // PlayersToSpawnPacket.TryAdd(profileId, queuedPacket); - - // // if (!PlayersToSpawn.ContainsKey(profileId)) - // // PlayersToSpawn.TryAdd(profileId, ESpawnState.None); - - // // if (queuedPacket.ContainsKey("isAI")) - // // Logger.LogDebug($"{nameof(ReadFromServerCharacters)}:isAI:{queuedPacket["isAI"]}"); - - // // if (queuedPacket.ContainsKey("isAI") && queuedPacket["isAI"].ToString() == "True" && !ProfileIdsAI.Contains(profileId)) - // // { - // // ProfileIdsAI.Add(profileId); - // // Logger.LogDebug($"Added AI Character {profileId} to {nameof(ProfileIdsAI)}"); - // // } - - // // if (queuedPacket.ContainsKey("isAI") && queuedPacket["isAI"].ToString() == "False" && !ProfileIdsUser.Contains(profileId)) - // // { - // // ProfileIdsUser.Add(profileId); - // // Logger.LogDebug($"Added User Character {profileId} to {nameof(ProfileIdsUser)}"); - // // } - - // // } - // // } - // // } - // // } - // //} - // //catch (Exception ex) - // //{ - - // // Logger.LogError(ex.ToString()); - - // //} - // //finally - // //{ - - // //} - //} - //private IEnumerator ProcessServerCharacters() private void ProcessServerCharacters() { @@ -1419,8 +1169,8 @@ private LocalPlayer SpawnCharacter(Profile profile, Vector3 position, int player // Cant use ObservedPlayerMode, it causes the player to fall through the floor and die , BackendConfigManager.Config.CharacterController.ObservedPlayerMode //, BackendConfigManager.Config.CharacterController.ClientPlayerMode - , () => Singleton.Instance.Control.Settings.MouseSensitivity - , () => Singleton.Instance.Control.Settings.MouseAimingSensitivity + , () => Singleton.Instance.Control.Settings.MouseSensitivity + , () => Singleton.Instance.Control.Settings.MouseAimingSensitivity , FilterCustomizationClass.Default , null , isYourPlayer: false @@ -1448,8 +1198,8 @@ private LocalPlayer SpawnCharacter(Profile profile, Vector3 position, int player Singleton.Instance.RegisterPlayer(otherPlayer); - if (!SpawnedPlayers.ContainsKey(profile.ProfileId)) - SpawnedPlayers.Add(profile.ProfileId, otherPlayer); + if (!SpawnedCharacters.ContainsKey(profile.ProfileId)) + SpawnedCharacters.Add(profile.ProfileId, otherPlayer); // Create/Add PlayerReplicatedComponent to the LocalPlayer // This shouldn't be needed. Handled in CoopPlayer.Create code @@ -1567,74 +1317,38 @@ public void SetWeaponInHandsOfNewPlayer(EFT.Player person, Action successCallbac }); } - private Dictionary LastPlayerHealthPackets = new(); + private HashSet DespawnList = new(); + + public void AddToDespawnList(string ProfileId) => DespawnList.Add(ProfileId); - private void CreatePlayerStatePacketFromPRC(ref List playerStates, EFT.Player player) + private bool DespawnCharacter(string ProfileId) { - // What this does is create a ISITPacket for the Character's health that can be SIT Serialized. - PlayerHealthPacket playerHealth = new PlayerHealthPacket(player.ProfileId); - playerHealth.IsAlive = player.HealthController.IsAlive; - playerHealth.Energy = player.HealthController.Energy.Current; - playerHealth.Hydration = player.HealthController.Hydration.Current; - var bpIndex = 0; - // Iterate over the BodyParts - foreach (EBodyPart bodyPart in Enum.GetValues(typeof(EBodyPart))) + //Player is already existent and should have already spawned, we can proceed immediately to removal. + if (SpawnedCharacters.ContainsKey(ProfileId)) { - var health = player.HealthController.GetBodyPartHealth(bodyPart); - playerHealth.BodyParts[bpIndex] = new PlayerBodyPartHealthPacket(); - playerHealth.BodyParts[bpIndex].BodyPart = bodyPart; - playerHealth.BodyParts[bpIndex].Current = health.Current; - playerHealth.BodyParts[bpIndex].Maximum = health.Maximum; - bpIndex++; - } - if (player.HealthController is SITHealthController sitHealthController) - { - // Paulov: TODO: Continue from here in another branch - var tmpHealthEffectPacketList = new List(); - while (sitHealthController.PlayerHealthEffectPackets.TryDequeue(out var p)) - { - tmpHealthEffectPacketList.Add(p); - } - playerHealth.HealthEffectPackets = tmpHealthEffectPacketList.ToArray(); - } + var Bot = SpawnedCharacters[ProfileId]; - if (playerHealth != null) - { - if (!LastPlayerHealthPackets.ContainsKey(player.ProfileId)) - LastPlayerHealthPackets.Add(player.ProfileId, playerHealth); + //Bleeding causes an exception on despawn, stop the bleeding effect. + Singleton.Instance.EffectsCommutator.StopBleedingForPlayer(Bot); - LastPlayerHealthPackets[player.ProfileId] = playerHealth; + Bot.Dispose(); + if (Bot.gameObject != null) + DestroyImmediate(Bot.gameObject); + + Destroy(Bot); + ProfileIdsAI.Remove(ProfileId); + SpawnedCharacters.Remove(ProfileId); + Players.TryRemove(ProfileId, out _); + return true; } - // Create the ISITPacket for the Character's Current State - PlayerStatePacket playerStatePacket = new PlayerStatePacket( - player.ProfileId - , player.Position - , player.Rotation - , player.HeadRotation - , player.MovementContext.MovementDirection - , player.MovementContext.CurrentState.Name - , player.MovementContext.Tilt - , player.MovementContext.Step - , player.MovementContext.CurrentAnimatorStateIndex - , player.MovementContext.CharacterMovementSpeed - , player.MovementContext.IsInPronePose - , player.MovementContext.PoseLevel - , player.MovementContext.IsSprintEnabled - , player.InputDirection - , player.MovementContext.LeftStanceController.LastAnimValue - , playerHealth - , player.Physical.SerializationStruct - , player.MovementContext.BlindFire - , player.MovementContext.ActualLinearVelocity - ); - ; - - // Add the serialized packets to the PlayerStates JArray - playerStates.Add(playerStatePacket); + Logger.LogWarning($"Character ({ProfileId}) has not spawned yet! Cannot remove"); + return false; } + private Dictionary LastPlayerHealthPackets = new(); + private DateTime LastPlayerStateSent { get; set; } = DateTime.Now; public ulong LocalIndex { get; set; } diff --git a/Source/Coop/Components/CoopGameComponents/SITGameExtractionComponent.cs b/Source/Coop/Components/CoopGameComponents/SITGameExtractionComponent.cs new file mode 100644 index 000000000..b67ffd8dc --- /dev/null +++ b/Source/Coop/Components/CoopGameComponents/SITGameExtractionComponent.cs @@ -0,0 +1,154 @@ +using BepInEx.Logging; +using Comfort.Common; +using EFT.Counters; +using EFT; +using EFT.Interactive; +using HarmonyLib.Tools; +using StayInTarkov.Coop.Players; +using StayInTarkov.Coop.SITGameModes; +using StayInTarkov.Coop.Web; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using StayInTarkov.Networking; +using StayInTarkov.Coop.NetworkPacket.Raid; + +namespace StayInTarkov.Coop.Components.CoopGameComponents +{ + public sealed class SITGameExtractionComponent : MonoBehaviour + { + ManualLogSource Logger { get; set; } + HashSet ExtractedProfilesSent {get;set;} = new HashSet(); + + + void Awake() + { + Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(SITGameExtractionComponent)); + } + + void Update() + { + if (!Singleton.Instantiated) + return; + + if (!Singleton.Instantiated) + return; + + ProcessExtractingPlayers(); + ProcessExtractionRequirements(); + HideExtractedPlayers(); + + } + + private void HideExtractedPlayers() + { + var world = Singleton.Instance; + var gameInstance = Singleton.Instance; + + // Hide extracted Players + foreach (var profileId in gameInstance.ExtractedPlayers) + { + var player = world.RegisteredPlayers.Find(x => x.ProfileId == profileId) as EFT.Player; + if (player == null) + continue; + + if (!ExtractedProfilesSent.Contains(profileId)) + { + ExtractedProfilesSent.Add(profileId); + if (player.Profile.Side == EPlayerSide.Savage) + { + player.Profile.EftStats.SessionCounters.AddDouble(0.01, + [ + CounterTag.FenceStanding, + EFenceStandingSource.ExitStanding + ]); + } + // Send the Extracted Packet to other Clients + GameClient.SendData(new ExtractedPlayerPacket(profileId).Serialize()); + } + + if (player.ActiveHealthController != null) + { + if (!player.ActiveHealthController.MetabolismDisabled) + { + player.ActiveHealthController.AddDamageMultiplier(0); + player.ActiveHealthController.SetDamageCoeff(0); + player.ActiveHealthController.DisableMetabolism(); + player.ActiveHealthController.PauseAllEffects(); + } + } + + //force close all screens to disallow looting open crates after extract + if (profileId == world.MainPlayer.ProfileId) + { + ScreenManager instance = ScreenManager.Instance; + instance.CloseAllScreensForced(); + } + + PlayerUtils.MakeVisible(player, false); + } + } + + private void ProcessExtractionRequirements() + { + var gameInstance = Singleton.Instance; + // Trigger all countdown exfils (e.g. car), clients are responsible for their own extract + // since exfilpoint.Entered is local because of collision logic being local + // we start from the end because we remove as we go in `CoopSITGame.ExfiltrationPoint_OnStatusChanged` + for (int i = gameInstance.EnabledCountdownExfils.Count - 1; i >= 0; i--) + { + var ep = gameInstance.EnabledCountdownExfils[i]; + if (gameInstance.PastTime - ep.ExfiltrationStartTime >= ep.Settings.ExfiltrationTime) + { + var game = Singleton.Instance; + foreach (var player in ep.Entered) + { + var hasUnmetRequirements = ep.UnmetRequirements(player).Any(); + if (player != null && player.HealthController.IsAlive && !hasUnmetRequirements) + { + game.ExtractingPlayers.Remove(player.ProfileId); + game.ExtractedPlayers.Add(player.ProfileId); + } + } + ep.SetStatusLogged(ep.Reusable ? EExfiltrationStatus.UncompleteRequirements : EExfiltrationStatus.NotPresent, nameof(ProcessExtractionRequirements)); + } + } + } + + private void ProcessExtractingPlayers() + { + var gameInstance = Singleton.Instance; + var playersToExtract = new HashSet(); + foreach (var exfilPlayer in gameInstance.ExtractingPlayers) + { + var exfilTime = new TimeSpan(0, 0, (int)exfilPlayer.Value.Item1); + var timeInExfil = new TimeSpan(DateTime.Now.Ticks - exfilPlayer.Value.Item2); + if (timeInExfil >= exfilTime) + { + if (!playersToExtract.Contains(exfilPlayer.Key)) + { +#if DEBUG + Logger.LogDebug(exfilPlayer.Key + " should extract"); +#endif + playersToExtract.Add(exfilPlayer.Key); + } + } +#if DEBUG + else + { + Logger.LogDebug(exfilPlayer.Key + " extracting " + timeInExfil); + } +#endif + } + + foreach (var player in playersToExtract) + { + gameInstance.ExtractingPlayers.Remove(player); + gameInstance.ExtractedPlayers.Add(player); + } + } + } +} diff --git a/Source/Coop/Components/CoopGameComponents/SITGameGCComponent.cs b/Source/Coop/Components/CoopGameComponents/SITGameGCComponent.cs new file mode 100644 index 000000000..518800f79 --- /dev/null +++ b/Source/Coop/Components/CoopGameComponents/SITGameGCComponent.cs @@ -0,0 +1,102 @@ +using Comfort.Common; +using EFT; +using StayInTarkov.Configuration; +using StayInTarkov.Memory; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace StayInTarkov.Coop.Components.CoopGameComponents +{ + public sealed class SITGameGCComponent : MonoBehaviour + { + private DateTime LastTimeRun { get; set; } = DateTime.MinValue; + private BepInEx.Logging.ManualLogSource Logger { get; set; } + + private int LastNumberOfPlayers { get; set; } + + private int NumberOfAlivePlayers => Singleton.Instance.AllAlivePlayersList.Count; + + #region Unity methods + + void Awake() + { + Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(SITGameGCComponent)); + Logger.LogDebug($"{nameof(SITGameGCComponent)}:{nameof(Awake)}"); + } + + void Start() + { + Logger.LogDebug($"{nameof(SITGameGCComponent)}:{nameof(Start)}"); + GarbageCollect(); + } + + void Update() + { + if (!PluginConfigSettings.Instance.AdvancedSettings.SETTING_EnableSITGC) + return; + + if ((DateTime.Now - LastTimeRun).TotalSeconds > PluginConfigSettings.Instance.AdvancedSettings.SETTING_SITGCMemoryCheckTime) + { + LastTimeRun = DateTime.Now; + GarbageCollectSIT(); + } + + if (NumberOfAlivePlayers != LastNumberOfPlayers) + { + LastNumberOfPlayers = NumberOfAlivePlayers; + BSGMemoryGC.Collect(force: false); + } + } + + #endregion + + private void GarbageCollect() + { + Logger.LogDebug($"{nameof(GarbageCollect)}"); + BSGMemoryGC.RunHeapPreAllocation(); + BSGMemoryGC.Collect(force: true); + BSGMemoryGC.EmptyWorkingSet(); + BSGMemoryGC.GCEnabled = true; + Resources.UnloadUnusedAssets(); + } + + /// + /// Runs the Garbage Collection + /// + /// + private void GarbageCollectSIT() + { + var nearestEnemyDist = float.MaxValue; + foreach (var p in Singleton.Instance.AllAlivePlayersList) + { + if (p.ProfileId == Singleton.Instance.MainPlayer.ProfileId) + continue; + + var dist = Vector3.Distance(p.Transform.position, Singleton.Instance.MainPlayer.Transform.position); + if (dist < nearestEnemyDist) + nearestEnemyDist = dist; + } + + if (nearestEnemyDist > 10) + { + var mem = MemoryInfo.GetCurrentStatus(); + if (mem == null) + { + return; + } + + var memPercentInUse = mem.dwMemoryLoad; + Logger.LogDebug($"Total memory used: {mem.dwMemoryLoad}%"); + if (memPercentInUse > PluginConfigSettings.Instance.AdvancedSettings.SETTING_SITGCMemoryThreshold) + GarbageCollect(); + + } + } + + } +} diff --git a/Source/Coop/Components/CoopGameComponents/SITGameGUIComponent.cs b/Source/Coop/Components/CoopGameComponents/SITGameGUIComponent.cs index 470c8bb99..58f28a3ea 100644 --- a/Source/Coop/Components/CoopGameComponents/SITGameGUIComponent.cs +++ b/Source/Coop/Components/CoopGameComponents/SITGameGUIComponent.cs @@ -23,7 +23,6 @@ public class SITGameGUIComponent : MonoBehaviour GUIStyle middleLargeLabelStyle; GUIStyle normalLabelStyle; - private ISITGame LocalGameInstance { get; } = Singleton.Instance; private SITGameComponent CoopGameComponent { get { return SITGameComponent.GetCoopGameComponent(); } } private ConcurrentDictionary Players => CoopGameComponent?.Players; @@ -47,8 +46,6 @@ public class SITGameGUIComponent : MonoBehaviour void Awake() { - // ---------------------------------------------------- - // Create a BepInEx Logger for CoopGameComponent Logger = BepInEx.Logging.Logger.CreateLogSource($"{nameof(SITGameGUIComponent)}"); Logger.LogDebug($"{nameof(SITGameGUIComponent)}:Awake"); @@ -57,8 +54,6 @@ void Awake() void OnGUI() { - - if (normalLabelStyle == null) { normalLabelStyle = new GUIStyle(GUI.skin.label); @@ -95,15 +90,6 @@ void OnGUI() var numberOfPlayersDead = Users.Count(x => !x.HealthController.IsAlive); - if (LocalGameInstance == null) - return; - - var coopGame = LocalGameInstance as CoopSITGame; - if (coopGame == null) - return; - - //rect = DrawSITStats(rect, numberOfPlayersDead, coopGame); - var quitState = CoopGameComponent.GetQuitState(); switch (quitState) { @@ -130,7 +116,6 @@ void OnGUI() break; } - //OnGUI_DrawPlayerList(rect); OnGUI_DrawPlayerFriendlyTags(rect); OnGUI_DrawPlayerEnemyTags(rect); @@ -160,7 +145,6 @@ private Rect DrawPing(Rect rect) _ => "", }; var text = new GUIContent($"{protocol}{(SITMatchmaking.IsClient ? "client" : "host")} ping:{gameclient.Ping} up:{gameclient.UploadSpeedKbps:0.00} down:{gameclient.DownloadSpeedKbps:0.00} loss:{gameclient.PacketLoss:0.00}% {(AkiBackendCommunication.Instance.HighPingMode ? "hpm" : "")}"); - // var newX = GUI.skin.label.CalcSize(text); GUI.Label(rect, text); GUI.contentColor = Color.white; @@ -207,7 +191,7 @@ private void OnGUI_DrawPlayerFriendlyTags(Rect rect) return; } - if (FPSCamera.Instance == null) + if (CameraClass.Instance == null) return; if (Players == null) @@ -222,8 +206,8 @@ private void OnGUI_DrawPlayerFriendlyTags(Rect rect) if (!Singleton.Instantiated) return; - if (FPSCamera.Instance.SSAA != null && FPSCamera.Instance.SSAA.isActiveAndEnabled) - screenScale = FPSCamera.Instance.SSAA.GetOutputWidth() / (float)FPSCamera.Instance.SSAA.GetInputWidth(); + if (CameraClass.Instance.SSAA != null && CameraClass.Instance.SSAA.isActiveAndEnabled) + screenScale = CameraClass.Instance.SSAA.GetOutputWidth() / (float)CameraClass.Instance.SSAA.GetInputWidth(); var ownPlayer = Singleton.Instance.MainPlayer; if (ownPlayer == null) @@ -306,7 +290,7 @@ private void OnGUI_DrawPlayerEnemyTags(Rect rect) return; } - if (FPSCamera.Instance == null) + if (CameraClass.Instance == null) return; if (Players == null) @@ -322,8 +306,8 @@ private void OnGUI_DrawPlayerEnemyTags(Rect rect) return; - if (FPSCamera.Instance.SSAA != null && FPSCamera.Instance.SSAA.isActiveAndEnabled) - screenScale = FPSCamera.Instance.SSAA.GetOutputWidth() / (float)FPSCamera.Instance.SSAA.GetInputWidth(); + if (CameraClass.Instance.SSAA != null && CameraClass.Instance.SSAA.isActiveAndEnabled) + screenScale = CameraClass.Instance.SSAA.GetOutputWidth() / (float)CameraClass.Instance.SSAA.GetInputWidth(); var ownPlayer = Singleton.Instance.MainPlayer; if (ownPlayer == null) @@ -360,36 +344,5 @@ private void OnGUI_DrawPlayerEnemyTags(Rect rect) } } - private void OnGUI_DrawPlayerList(Rect rect) - { - if (!PluginConfigSettings.Instance.CoopSettings.SETTING_DEBUGShowPlayerList) - return; - - rect.y += 15; - - if (Singleton.Instance != null) - { - var players = Singleton.Instance.RegisteredPlayers.ToList(); - players.AddRange(Players.Values); - players = players.Distinct(x => x.ProfileId).ToList(); - - rect.y += 15; - GUI.Label(rect, $"{StayInTarkovPlugin.LanguageDictionary["PLAYERS_COUNT"]} [{players.Count}]:"); - // - rect.y += 15; - foreach (var p in players) - { - string aiPlayerText = (string)(p.IsAI ? StayInTarkovPlugin.LanguageDictionary["AI-SESSION"] : StayInTarkovPlugin.LanguageDictionary["PLAYER-SESSION"]); - string aliveDeadText = (string)(p.HealthController.IsAlive ? StayInTarkovPlugin.LanguageDictionary["ALIVE"] : StayInTarkovPlugin.LanguageDictionary["DEAD"]); - - GUI.Label(rect, $"{p.Profile.Nickname}:{aiPlayerText}:{aliveDeadText}"); - //GUI.Label(rect, $"{p.Profile.Nickname}:{(p.IsAI ? "AI" : "Player")}:{(p.HealthController.IsAlive ? "Alive" : "Dead")}"); - rect.y += 15; - } - - players.Clear(); - players = null; - } - } } } \ No newline at end of file diff --git a/Source/Coop/Components/CoopGameComponents/SITGameTimeAndWeatherSyncComponent.cs b/Source/Coop/Components/CoopGameComponents/SITGameTimeAndWeatherSyncComponent.cs new file mode 100644 index 000000000..25ff730c7 --- /dev/null +++ b/Source/Coop/Components/CoopGameComponents/SITGameTimeAndWeatherSyncComponent.cs @@ -0,0 +1,71 @@ +using Comfort.Common; +using EFT; +using EFT.Weather; +using StayInTarkov.Coop.NetworkPacket.Raid; +using StayInTarkov.Coop.SITGameModes; +using System; +using UnityEngine.Networking; + +namespace StayInTarkov.Coop.Components.CoopGameComponents +{ + public sealed class SITGameTimeAndWeatherSyncComponent : NetworkBehaviour + { + private DateTime LastTimeSent = DateTime.MinValue; + + private BepInEx.Logging.ManualLogSource Logger { get; set; } + + void Awake() + { + Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(SITGameTimeAndWeatherSyncComponent)); + Logger.LogDebug($"{nameof(SITGameTimeAndWeatherSyncComponent)}:{nameof(Awake)}"); + } + + void Start() + { + Logger.LogDebug($"{nameof(SITGameTimeAndWeatherSyncComponent)}:{nameof(Start)}"); + } + + + void Update() + { + if ((DateTime.Now - LastTimeSent).Seconds > 15) + { + LastTimeSent = DateTime.Now; + + TimeAndWeatherPacket packet = new(); + + var sitGame = Singleton.Instance; + + if (sitGame.GameDateTime != null) + packet.GameDateTime = sitGame.GameDateTime.Calculate().Ticks; + + var weatherController = WeatherController.Instance; + if (weatherController != null) + { + if (weatherController.CloudsController != null) + packet.CloudDensity = weatherController.CloudsController.Density; + + var weatherCurve = weatherController.WeatherCurve; + if (weatherCurve != null) + { + packet.Fog = weatherCurve.Fog; + packet.LightningThunderProbability = weatherCurve.LightningThunderProbability; + packet.Rain = weatherCurve.Rain; + packet.Temperature = weatherCurve.Temperature; + packet.Wind = weatherCurve.Wind; + packet.TopWind = weatherCurve.TopWind; + } + + Networking.GameClient.SendData(packet.Serialize()); + } + + if (sitGame.GameTimer.StartDateTime.HasValue && sitGame.GameTimer.SessionTime.HasValue) + { + RaidTimerPacket raidTimerPacket = new(); + raidTimerPacket.SessionTime = (sitGame.GameTimer.SessionTime - sitGame.GameTimer.PastTime).Value.Ticks; + Networking.GameClient.SendData(raidTimerPacket.Serialize()); + } + } + } + } +} diff --git a/Source/Coop/Components/IPlayerPacketHandler.cs b/Source/Coop/Components/IPlayerPacketHandler.cs deleted file mode 100644 index 1f7b4d6e8..000000000 --- a/Source/Coop/Components/IPlayerPacketHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace StayInTarkov.Coop.Components -{ - internal interface IPlayerPacketHandler - { - public void ProcessPacket(Dictionary packet); - public void ProcessPacket(byte[] packet); - } -} diff --git a/Source/Coop/Components/SITMatchmakerGUIComponent.cs b/Source/Coop/Components/SITMatchmakerGUIComponent.cs index c38220dfb..e52c4a09f 100644 --- a/Source/Coop/Components/SITMatchmakerGUIComponent.cs +++ b/Source/Coop/Components/SITMatchmakerGUIComponent.cs @@ -3,9 +3,11 @@ using EFT.Bots; using EFT.UI; using EFT.UI.Matchmaker; +using HarmonyLib.Tools; using Newtonsoft.Json.Linq; using StayInTarkov.Configuration; using StayInTarkov.Coop.Matchmaker; +using StayInTarkov.Coop.SITGameModes; using StayInTarkov.Networking; using StayInTarkov.UI; using System; @@ -13,14 +15,20 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; +using Systems.Effects; using UnityEngine; +using UnityEngine.Yoga; using Color = UnityEngine.Color; using FontStyle = UnityEngine.FontStyle; namespace StayInTarkov.Coop.Components { + /// + /// This is the "Server Browser" and replacement of the "last screen" + /// internal class SITMatchmakerGUIComponent : MonoBehaviour { private UnityEngine.Rect windowRect = new(20, 20, 120, 50); @@ -40,7 +48,7 @@ internal class SITMatchmakerGUIComponent : MonoBehaviour private Task GetMatchesTask { get; set; } - private Dictionary[] m_Matches { get; set; } + private JArray m_Matches { get; set; } private CancellationTokenSource m_cancellationTokenSource; @@ -53,7 +61,9 @@ internal class SITMatchmakerGUIComponent : MonoBehaviour private int botAmountInput = 0; private int botDifficultyInput = 0; - private int protocolInput = 0; + private int protocolInput = 1; + private bool p2pModeSelected => protocolInput == 1; + private string pendingServerId = ""; private int p2pModeOptionInput; @@ -91,6 +101,10 @@ internal class SITMatchmakerGUIComponent : MonoBehaviour public Profile Profile { get; internal set; } public Rect hostGameWindowInnerRect { get; private set; } + public bool IsMatchmakingAvailable { get; private set; } = false; + + public bool IsHeadlessServerAvailable { get; private set; } = false; + #region TextMeshPro Game Objects private GameObject GOIPv4_Text { get; set; } @@ -118,20 +132,7 @@ void Start() Logger.LogInfo("Start"); TMPManager = new PaulovTMPManager(); - //DrawIPAddresses(); - //DrawSITButtons(); - //// Get Canvas - //Canvas = GameObject.FindObjectOfType(); - //if (Canvas != null) - //{ - // Logger.LogInfo("Canvas found"); - // foreach (Transform b in Canvas.GetComponents()) - // { - // Logger.LogInfo(b); - // } - // //Canvas.GetComponent(); - //} - + styleStateBrowserBigButtonsNormal = new GUIStyleState() { textColor = Color.white @@ -142,14 +143,6 @@ void Start() texture2D.Fill(Color.black); styleStateBrowserBigButtonsNormal.background = texture2D; styleStateBrowserBigButtonsNormal.textColor = Color.black; - //styleStateBrowserWindowNormal.background = texture2D; - //styleStateBrowserWindowNormal.textColor = Color.white; - - // Create Skin for Window - //GUISkin skin = ScriptableObject.CreateInstance(); - //skin.window = new GUIStyle(); - //skin.window.alignment = TextAnchor.MiddleLeft; - //skin.window.normal = styleStateBrowserWindowNormal; m_cancellationTokenSource = new CancellationTokenSource(); styleBrowserBigButtons = new GUIStyle() @@ -170,41 +163,71 @@ void Start() StartCoroutine(ResolveMatches()); DisableBSGButtons(); + SITRearrangeScreen(); + + DeleteExistingMatches(); + } + + private GameObject SITServersAvailable { get; set; } + private GameObject MatchmakingServersAvailable { get; set; } + + private void SITRearrangeScreen() + { var previewsPanel = GameObject.Find("PreviewsPanel"); if (previewsPanel != null) { var previewsPanelRect = previewsPanel.GetComponent(); - previewsPanelRect.position = new Vector3(400, 300, 0); + previewsPanelRect.position = new Vector3(400, 290, 0); + + var levelPanel = GameObject.Find("Level Panel"); + if (levelPanel != null) + { + var levelPanelRect = levelPanel.GetComponent(); + levelPanelRect.position = new Vector3(150, Screen.height, 0); + } } var playerImage = GameObject.Find("PlayerImage"); if (playerImage != null) { var playerImageRect = playerImage.GetComponent(); - playerImageRect.localScale = new Vector3(1.4f, 1.4f, 0); + playerImageRect.localScale = new Vector3(1.39f, 1.39f, 0); } - DeleteExistingMatches(); + var playerName = GameObject.Find("PlayerInfo"); + if (playerName != null) + { + var playerNameRect = playerName.GetComponent(); + playerNameRect.position = new Vector3(Screen.width * 0.2f, Screen.height - (Screen.height * 0.02f), 0); + playerNameRect.localScale = new Vector3(1.35f, 1.35f, 0); + + } + + ScreenCaption = GameObject.Find("Screen Caption"); + if (ScreenCaption != null) + { + Logger.LogInfo("Found Screen Caption"); + ScreenCaption.gameObject.SetActive(false); + } + + Subcaption = GameObject.Find("Subcaption"); + if (ScreenCaption != null) + { + Logger.LogInfo("Found Subcaption"); + Subcaption.gameObject.SetActive(false); + } + + SITServersAvailable = this.TMPManager.InstantiateTarkovTextLabel(nameof(SITServersAvailable), "", 20, new Vector3(((Screen.height / 2)) - 100, ((Screen.height / 2)) - 60, 0)); } private void DeleteExistingMatches() { JObject jsonObj = new JObject(); jsonObj.Add("serverId", AkiBackendCommunication.Instance.ProfileId); - AkiBackendCommunication.Instance.PostJson("/coop/server/delete", jsonObj.ToString()); + _ = AkiBackendCommunication.Instance.PostJsonAsync("/coop/server/delete", jsonObj.ToString()); } - //private void DrawIPAddresses() - //{ - // var GOIPv4_Text = TMPManager.InstantiateTarkovTextLabel("GOIPv4_Text", $"IPv4: {SITMatchmaking.IPAddress}", 16, new Vector3(0, (Screen.height / 2) - 120, 0)); - // TMPManager.InstantiateTarkovTextLabel("GOIPv4_Text", GOIPv4_Text.transform, $"IPv6: {SITIPAddressManager.SITIPAddresses.ExternalAddresses.IPAddressV6}", 16, new Vector3(0, -20, 0)); - //} - - //private void DrawSITButtons() - //{ - // //TMPManager.InstantiateTarkovButton("test_btn", "Test", 16, new Vector3(0, (Screen.height / 2) - 120, 0)); - //} - + void OnDestroy() { if (m_cancellationTokenSource != null) @@ -213,8 +236,25 @@ void OnDestroy() StopAllTasks = true; } + GameObject ScreenCaption { get; set; } + GameObject Subcaption { get; set; } + void Update() { + if(this.m_Matches != null) + { + var countOfAvailableServers = + // Count open servers that are Waiting for Players + this.m_Matches + .Count(match => match["Status"]?.ToString() == SITMPRaidStatus.WaitingForPlayers.ToString()); + + var serverAvailabilityText = $"{countOfAvailableServers} Servers Available " + + $"{(this.IsMatchmakingAvailable ? " | Matchmaking Available" : "")}" + + $"{(this.IsHeadlessServerAvailable ? " | Headless Server Available" : "")}"; + + this.SITServersAvailable.GetComponent().text = serverAvailabilityText; + } + if (Input.GetKeyDown(KeyCode.Escape)) { DestroyThis(); @@ -224,6 +264,9 @@ void Update() void OnGUI() { + // Draw the horizontal line for top bar + GUI.DrawTexture(new Rect(0, Screen.height * 0.08f, Screen.width, 2), Texture2D.grayTexture); + // Define the proportions for the main window and the host game window (same size) var windowWidthFraction = 0.4f; var windowHeightFraction = 0.4f; @@ -348,7 +391,6 @@ private void DisableBSGButtons() OriginalBackButton.gameObject.SetActive(false); OriginalBackButton.enabled = false; OriginalBackButton.Interactable = false; - } void GetMatches() @@ -358,10 +400,10 @@ void GetMatches() { while (!StopAllTasks) { - var result = await AkiBackendCommunication.Instance.PostJsonAsync[]>("/coop/server/getAllForLocation", RaidSettings.ToJson(), timeout: 4000, debug: false); + var result = await AkiBackendCommunication.Instance.PostJsonAsync("/coop/server/getAllForLocation", RaidSettings.ToJson(), timeout: 4000); if (result != null) { - m_Matches = result; + m_Matches = JArray.Parse(result); } if (ct.IsCancellationRequested) @@ -369,7 +411,7 @@ void GetMatches() ct.ThrowIfCancellationRequested(); } - await Task.Delay(7000); + await Task.Delay(5000); if (ct.IsCancellationRequested) { @@ -447,7 +489,6 @@ void DrawBrowserWindow(int windowID) // Use the Language Dictionary string[] columnLabels = { StayInTarkovPlugin.LanguageDictionary["SERVER"].ToString() - , StayInTarkovPlugin.LanguageDictionary["PLAYERS"].ToString() , StayInTarkovPlugin.LanguageDictionary["LOCATION"].ToString() , StayInTarkovPlugin.LanguageDictionary["PASSWORD"].ToString() }; @@ -507,46 +548,44 @@ void DrawBrowserWindow(int windowID) // Reset the GUI.backgroundColor to its original state GUI.backgroundColor = Color.white; - if (m_Matches != null) - { - var index = 0; - var yPosOffset = 60; + if (m_Matches == null) + return; - foreach (var match in m_Matches) - { - var yPos = yPosOffset + index * (cellHeight + 5); + //Logger.LogDebug(m_Matches.ToString()); - //Extract player count from match before the server is shown - int playerCount = int.Parse(match["PlayerCount"].ToString()); - string protocol = (string)match["Protocol"]; + var index = 0; + var yPosOffset = 60; - if (playerCount > 0 || protocol == "PeerToPeerUdp") - { - // Display Host Name with "Raid" label - GUI.Label(new UnityEngine.Rect(10, yPos, cellWidth - separatorWidth, cellHeight), $"{match["HostName"]} Raid", labelStyle); + foreach (var match in m_Matches) + { + var yPos = yPosOffset + index * (cellHeight + 5); - // Display Player Count - GUI.Label(new UnityEngine.Rect(cellWidth, yPos, cellWidth - separatorWidth, cellHeight), match["PlayerCount"].ToString(), labelStyle); + string protocol = match["Protocol"]?.ToString(); + string status = match["Status"]?.ToString(); - // Display Location - GUI.Label(new UnityEngine.Rect(cellWidth * 2, yPos, cellWidth - separatorWidth, cellHeight), match["Location"].ToString(), labelStyle); + // Display Host Name with "Raid" label + GUI.Label(new UnityEngine.Rect(10, yPos, cellWidth - separatorWidth, cellHeight), $"{match["HostName"]} Raid", labelStyle); - // Display Password Locked - GUI.Label(new UnityEngine.Rect(cellWidth * 3, yPos, cellWidth - separatorWidth, cellHeight), bool.Parse(match["IsPasswordLocked"].ToString()) ? (string)StayInTarkovPlugin.LanguageDictionary["PASSWORD-YES"] : "", labelStyle); + // Display Location + GUI.Label(new UnityEngine.Rect(cellWidth * 1, yPos, cellWidth - separatorWidth, cellHeight), match["Location"].ToString(), labelStyle); - // Calculate the width of the combined server information (Host Name, Player Count, Location) - var serverInfoWidth = cellWidth * 3 - separatorWidth * 2; + // Display Password Locked + GUI.Label(new UnityEngine.Rect(cellWidth * 2, yPos, cellWidth - separatorWidth, cellHeight), bool.Parse(match["IsPasswordLocked"].ToString()) ? (string)StayInTarkovPlugin.LanguageDictionary["PASSWORD-YES"] : "", labelStyle); - // Create "Join" button for each match on the next column - if (GUI.Button(new UnityEngine.Rect(cellWidth * 4 + separatorWidth / 2 + 15, yPos + (cellHeight * 0.3f), cellWidth * 0.8f, cellHeight * 0.5f), StayInTarkovPlugin.LanguageDictionary["JOIN"].ToString(), buttonStyle)) - { - // Perform actions when the "Join" button is clicked - JoinMatch(SITMatchmaking.Profile.ProfileId, match["ServerId"].ToString()); - } + // Calculate the width of the combined server information (Host Name, Player Count, Location) + var serverInfoWidth = cellWidth * 3 - separatorWidth * 2; - index++; + if (status == "WaitingForPlayers") + { + // Create "Join" button for each match on the next column + if (GUI.Button(new UnityEngine.Rect(cellWidth * 3 + separatorWidth / 2 + 15, yPos + (cellHeight * 0.3f), cellWidth * 0.8f, cellHeight * 0.5f), StayInTarkovPlugin.LanguageDictionary["JOIN"].ToString(), buttonStyle)) + { + // Perform actions when the "Join" button is clicked + JoinMatch(SITMatchmaking.Profile.ProfileId, match["ServerId"].ToString()); } } + + index++; } } @@ -624,14 +663,6 @@ void DrawHostGameWindow(int windowID) { case 0: - //if (GameObject_NumberOfPlayersToWaitFor == null) - // GameObject_NumberOfPlayersToWaitFor = TMPManager.InstantiateTarkovTextLabel( - // nameof(GameObject_NumberOfPlayersToWaitFor) - // , StayInTarkovPlugin.LanguageDictionary["NUMBER_OF_PLAYERS_TO_WAIT_FOR_MESSAGE"] - // , 14 - // // for TMP, 0 is the middle of the screen - // , new Vector3(0, 0) - // ); // Title label for the number of players GUI.Label(new Rect(cols[0], y, calcSizeContentLabelNumberOfPlayers.x, calcSizeContentLabelNumberOfPlayers.y), StayInTarkovPlugin.LanguageDictionary["NUMBER_OF_PLAYERS_TO_WAIT_FOR_MESSAGE"].ToString(), labelStyle); @@ -707,7 +738,7 @@ void DrawHostGameWindow(int windowID) break; case 6: // If Peer to Peer is chosen - if (protocolInput == 1) + if (this.p2pModeSelected) { // P2P Mode Option Choice GUI.Label(new Rect(cols[0], y, labelStyle.CalcSize(new GUIContent(StayInTarkovPlugin.LanguageDictionary["P2P_MODE_LABEL"].ToString())).x, calcSizeContentLabelNumberOfPlayers.y), StayInTarkovPlugin.LanguageDictionary["P2P_MODE_LABEL"].ToString(), labelStyle); @@ -718,7 +749,7 @@ void DrawHostGameWindow(int windowID) break; case 7: // If Peer to Peer is chosen - if (protocolInput == 1) + if (this.p2pModeSelected) { // Automatic if (p2pModeOptionInput == 0) @@ -790,17 +821,16 @@ private void HostRaidAndJoin() joinPacket.Add("serverId", SITMatchmaking.Profile.ProfileId); joinPacket.Add("m", "JoinMatch"); AkiBackendCommunication.Instance.PostDownWebSocketImmediately(joinPacket.SITToJson()); - //AkiBackendCommunication.Instance.PostJson("coop/server/update", joinPacket.SITToJson()); DestroyThis(); } - private void HostSoloRaidAndJoin() + public void HostSoloRaidAndJoin(ESITProtocol protocol = ESITProtocol.RelayTcp, EBotAmount botAmount = EBotAmount.AsOnline) { FixesHideoutMusclePain(); - RaidSettings.BotSettings.BotAmount = EBotAmount.AsOnline; - RaidSettings.WavesSettings.BotAmount = EBotAmount.AsOnline; + RaidSettings.BotSettings.BotAmount = botAmount; + RaidSettings.WavesSettings.BotAmount = botAmount; RaidSettings.WavesSettings.BotDifficulty = EBotDifficulty.AsOnline; RaidSettings.WavesSettings.IsBosses = true; @@ -810,7 +840,7 @@ private void HostSoloRaidAndJoin() SITMatchmaking.Profile.ProfileId , RaidSettings , "" - , ESITProtocol.RelayTcp + , protocol , null , PluginConfigSettings.Instance.CoopSettings.UdpServerLocalPort, EMatchmakerType.GroupLeader); diff --git a/Source/Coop/Controllers/CoopInventory/CoopInventoryController.cs b/Source/Coop/Controllers/CoopInventory/CoopInventoryController.cs index 59589b098..608ed4929 100644 --- a/Source/Coop/Controllers/CoopInventory/CoopInventoryController.cs +++ b/Source/Coop/Controllers/CoopInventory/CoopInventoryController.cs @@ -59,6 +59,7 @@ public uint CreateOperation(T operation, Callback IgnoreOperations.Add(OperationToIgnore); public override void Execute(AbstractInventoryOperation operation, [CanBeNull] Callback callback) { @@ -84,6 +85,15 @@ public override void Execute(AbstractInventoryOperation operation, [CanBeNull] C /// protected virtual void SendExecuteOperationToServer(AbstractInventoryOperation operation) { + if(IgnoreOperations.Contains(operation.Id)) + { + StayInTarkovHelperConstants.Logger.LogWarning($"Ignoring operation: {operation.Id}"); + + //Ignore the operation once. + IgnoreOperations.Remove(operation.Id); + return; + } + var itemId = ""; var templateId = ""; ushort stackObjectsCount = 1; @@ -221,7 +231,7 @@ public static bool IsDiscardLimitsFine(Dictionary DiscardLimits) public override void CallMalfunctionRepaired(Weapon weapon) { base.CallMalfunctionRepaired(weapon); - if (!Player.IsAI && (bool)Singleton.Instance.Game.Settings.MalfunctionVisability) + if (!Player.IsAI && (bool)Singleton.Instance.Game.Settings.MalfunctionVisability) { MonoBehaviourSingleton.Instance.MalfunctionGlow.ShowGlow(BattleUIMalfunctionGlow.GlowType.Repaired, force: true, GetMalfunctionGlowAlphaMultiplier()); } diff --git a/Source/Coop/Controllers/CoopInventory/CoopInventoryControllerClient.cs b/Source/Coop/Controllers/CoopInventory/CoopInventoryControllerClient.cs index 9f406e07c..510c1a249 100644 --- a/Source/Coop/Controllers/CoopInventory/CoopInventoryControllerClient.cs +++ b/Source/Coop/Controllers/CoopInventory/CoopInventoryControllerClient.cs @@ -7,12 +7,11 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using static UnityEngine.UIElements.StyleVariableResolver; namespace StayInTarkov.Coop.Controllers.CoopInventory { public sealed class CoopInventoryControllerClient - : EFT.Player.PlayerOwnerInventoryController, ICoopInventoryController, IIdGenerator + : EFT.Player.PlayerInventoryController, ICoopInventoryController, IIdGenerator { ManualLogSource BepInLogger { get; set; } @@ -161,12 +160,14 @@ public void ReceiveExecute(AbstractInventoryOperation operation, Action callback } - public override SOperationResult123 TryThrowItem(Item item, Callback callback = null, bool silent = false) + + public override GStruct416 TryThrowItem(Item item, Callback callback = null, bool silent = false) { ThrowItem(item, null, callback); return true; } + public override bool CheckTransferOwners(Item item, ItemAddress targetAddress, out Error error) { error = null; diff --git a/Source/Coop/Controllers/HandControllers/SITFirearmController.cs b/Source/Coop/Controllers/HandControllers/SITFirearmController.cs index be2eddec6..4a95d7061 100644 --- a/Source/Coop/Controllers/HandControllers/SITFirearmController.cs +++ b/Source/Coop/Controllers/HandControllers/SITFirearmController.cs @@ -2,15 +2,11 @@ using Comfort.Common; using EFT; using EFT.InventoryLogic; -using EFT.UI; +using StayInTarkov.Coop.Controllers.CoopInventory; +using StayInTarkov.Coop.NetworkPacket.Player.Inventory; using StayInTarkov.Coop.NetworkPacket.Player.Weapons; -using StayInTarkov.Coop.Players; using StayInTarkov.Networking; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using UnityEngine; namespace StayInTarkov.Coop.Controllers.HandControllers @@ -27,10 +23,67 @@ public override void Spawn(float animationSpeed, Action callback) public override void Execute(IOperation1 operation, Callback callback) { + //Apply the same checks BSG does before invoking DropBackpackOperationInvoke + if (!method_18(operation)) + { + IOneItemOperation ProperOperation = TryGetIOneItemOperation(operation); + + if (ProperOperation != null && IsAnimatedSlot(ProperOperation)) + { + BepInLogger.LogInfo($"{nameof(SITFirearmController)}:Attempt to quickly replicate a backpack drop"); + + PlayerInventoryDropBackpackPacket packet = new(); + packet.ProfileId = _player.ProfileId; + + GameClient.SendData(packet.Serialize()); + + //Do not send the current operation to the inventory server as the packet we just sent already handles this. + AbstractInventoryOperation AbstractOperation = (AbstractInventoryOperation)operation; + var CoopInventoryController = ((CoopInventoryController)ItemFinder.GetPlayerInventoryController(_player)); + CoopInventoryController.IgnoreOperation(AbstractOperation.Id); + } + + } + BepInLogger.LogDebug($"{nameof(SITFirearmController)}:{nameof(Execute)}:{operation}"); base.Execute(operation, callback); } + private IOneItemOperation TryGetIOneItemOperation(IOperation1 operation) + { + if (!(operation is IOneItemOperation ItemOperation)) + return null; + + return ItemOperation; + } + + //Replicate IsAnimatedSlot as this method is not available to us. + private bool IsAnimatedSlot(IOneItemOperation ItemOperation) + { + try + { + if (ItemOperation == null) + return false; + + var PlyInventoryController = (InventoryControllerClass)ItemFinder.GetPlayerInventoryController(_player); + + //We either picked something up or something is messed up, dont continue in either case. + if (PlyInventoryController == null || ItemOperation.From1 == null) + return false; + + //I'm not doing BSG's dumb looping code here (At least that's what it looks like in ILSpy) for only one item in an array + if (PlyInventoryController.Inventory.Equipment.GetSlot(EquipmentSlot.Backpack) == ItemOperation.From1.Container) + return true; + + return false; + } + catch(Exception ex) + { + BepInLogger.LogError($"{nameof(SITFirearmController)}:{nameof(IsAnimatedSlot)}:{ex}"); + return false; + } + } + public override void Drop(float animationSpeed, Action callback, bool fastDrop = false, Item nextControllerItem = null) { base.Drop(animationSpeed, callback, fastDrop, nextControllerItem); @@ -67,9 +120,10 @@ public override void ReloadWithAmmo(AmmoPack ammoPack, Callback callback) { if (CanStartReload()) { - base.ReloadWithAmmo(ammoPack, callback); StayInTarkov.Coop.NetworkPacket.Player.Weapons.ReloadWithAmmoPacket packet = new (_player.ProfileId, ammoPack.GetReloadingAmmoIds()); GameClient.SendData(packet.Serialize()); + + base.ReloadWithAmmo(ammoPack, callback); } } @@ -281,11 +335,11 @@ public override bool ToggleLauncher() public override void CreateFlareShot(BulletClass flareItem, Vector3 shotPosition, Vector3 forward) { + Logger.LogDebug(nameof(CreateFlareShot)); var createFlareShotPacket = new CreateFlareShotPacket(_player.ProfileId, shotPosition, forward, flareItem.TemplateId); GameClient.SendData(createFlareShotPacket.Serialize()); base.CreateFlareShot(flareItem, shotPosition, forward); } - } } diff --git a/Source/Coop/Controllers/Health/SITHealthController.cs b/Source/Coop/Controllers/Health/SITHealthController.cs index dcf028829..c446f36cd 100644 --- a/Source/Coop/Controllers/Health/SITHealthController.cs +++ b/Source/Coop/Controllers/Health/SITHealthController.cs @@ -23,7 +23,7 @@ public sealed class SITHealthController : PlayerHealthController public EFT.Player _Player; - public SITHealthController(Profile.ProfileHealth healthInfo, EFT.Player player, InventoryControllerClass inventoryController, SkillManager skillManager, bool aiHealth) + public SITHealthController(ProfileHealth healthInfo, EFT.Player player, InventoryControllerClass inventoryController, SkillManager skillManager, bool aiHealth) : base(healthInfo, player, inventoryController, skillManager, aiHealth) { BepInLogger.LogDebug(this.GetType().Name); diff --git a/Source/Coop/Controllers/Health/SITHealthControllerClient.cs b/Source/Coop/Controllers/Health/SITHealthControllerClient.cs index ec049bb09..3b9bf6641 100644 --- a/Source/Coop/Controllers/Health/SITHealthControllerClient.cs +++ b/Source/Coop/Controllers/Health/SITHealthControllerClient.cs @@ -19,7 +19,7 @@ internal sealed class SITHealthControllerClient public override bool _sendNetworkSyncPackets => false; - public SITHealthControllerClient(Profile.ProfileHealth healthInfo, EFT.Player player, InventoryControllerClass inventoryController, SkillManager skillManager) + public SITHealthControllerClient(ProfileHealth healthInfo, EFT.Player player, InventoryControllerClass inventoryController, SkillManager skillManager) : base(healthInfo, player, inventoryController, skillManager, true) { BepInLogger = BepInEx.Logging.Logger.CreateLogSource(nameof(SITHealthControllerClient)); diff --git a/Source/Coop/CoopPatches.cs b/Source/Coop/CoopPatches.cs index 32a99959d..a72579ec0 100644 --- a/Source/Coop/CoopPatches.cs +++ b/Source/Coop/CoopPatches.cs @@ -13,6 +13,7 @@ using System.Reflection; using UnityEngine; using StayInTarkov.Coop.Components.CoopGameComponents; +using StayInTarkov.Coop.AI; namespace StayInTarkov.Coop { @@ -49,40 +50,17 @@ internal static void Run(BepInEx.Configuration.ConfigFile config) internal static List NoMRPPatches { get; } = new List(); - internal static GameObject CoopGameComponentParent { get; set; } - internal static void EnableDisablePatches() { + // Paulov: There is no reason to disable these anymore as all games are now MP var enablePatches = true; - var coopGC = SITGameComponent.GetCoopGameComponent(); - if (coopGC == null) - { - Logger.LogDebug($"CoopPatches:CoopGameComponent is null, Patches wont be Applied"); - enablePatches = false; - } - - if (coopGC != null && !coopGC.enabled) - { - Logger.LogDebug($"CoopPatches:CoopGameComponent is not enabled, Patches wont be Applied"); - enablePatches = false; - } - - if (string.IsNullOrEmpty(SITGameComponent.GetServerId())) - { - Logger.LogDebug($"CoopPatches:CoopGameComponent ServerId is not set, Patches wont be Applied"); - enablePatches = false; - } - if (!NoMRPPatches.Any()) { - //NoMRPPatches.Add(new Player_Init_Coop_Patch(m_Config)); - //NoMRPPatches.Add(new WeaponSoundPlayer_FireSonicSound_Patch()); - //NoMRPPatches.Add(new ItemControllerHandler_Move_Patch()); NoMRPPatches.Add(new LootableContainer_Interact_Patch()); + NoMRPPatches.Add(new BotDespawnPatch()); } - //Logger.LogInfo($"{NoMRPPatches.Count()} Non-MR Patches found"); foreach (var patch in NoMRPPatches) { if (enablePatches) @@ -119,7 +97,6 @@ internal static void EnableDisablePatches() if (!mrp.DisablePatch && enablePatches) { - //Logger.LogInfo($"Enabled {mrp.GetType()}"); mrp.Enable(); } else @@ -133,38 +110,10 @@ internal static void LeftGameDestroyEverything() if (SITGameComponent.TryGetCoopGameComponent(out var coopGameComponent)) { - //foreach (var p in coopGameComponent.Players) - //{ - // if (p.Value == null) - // continue; - - // if (p.Value.TryGetComponent(out var prc)) - // { - // GameObject.Destroy(prc); - // } - //} - - //foreach (var pl in GameObject.FindObjectsOfType()) - //{ - // GameObject.DestroyImmediate(pl); - //} - coopGameComponent.RunAsyncTasks = false; GameObject.DestroyImmediate(coopGameComponent); } - //foreach (var prc in GameObject.FindObjectsOfType()) - //{ - // GameObject.DestroyImmediate(prc); - //} - - - if (CoopGameComponentParent != null) - GameObject.DestroyImmediate(CoopGameComponentParent); - - //GCHelpers.DisableGC(true); - //GCHelpers.ClearGarbage(true, true); - AkiBackendCommunication.Instance.WebSocketClose(); EnableDisablePatches(); diff --git a/Source/Coop/CoopPlayerStatisticsManager.cs b/Source/Coop/CoopPlayerStatisticsManager.cs index 6d0982a34..d927e907e 100644 --- a/Source/Coop/CoopPlayerStatisticsManager.cs +++ b/Source/Coop/CoopPlayerStatisticsManager.cs @@ -1,11 +1,5 @@ using EFT; using EFT.HealthSystem; -using EFT.InventoryLogic; -using System; -using System.Collections.Generic; -using static Ragfair; -using UnityEngine; -using Comfort.Common; namespace StayInTarkov.Coop { diff --git a/Source/Coop/FreeCamera/FreeCamera.cs b/Source/Coop/FreeCamera/FreeCamera.cs index f516ec0ea..8c32df980 100644 --- a/Source/Coop/FreeCamera/FreeCamera.cs +++ b/Source/Coop/FreeCamera/FreeCamera.cs @@ -1,4 +1,9 @@ -using JetBrains.Annotations; +#nullable enable + +using StayInTarkov.Coop.Components.CoopGameComponents; +using StayInTarkov.Coop.Players; +using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace StayInTarkov.Coop.FreeCamera @@ -10,85 +15,216 @@ namespace StayInTarkov.Coop.FreeCamera /// https://gist.github.com/ashleydavis/f025c03a9221bc840a2b /// /// This is HEAVILY based on Terkoiz's work found here. Thanks for your work Terkoiz! - /// https://dev.sp-tarkov.com/Terkoiz/Freecam/raw/branch/master/project/Terkoiz.Freecam/FreecamController.cs + /// https://dev.sp-tarkov.com/Terkoiz/Freecam/raw/branch/master/project/Terkoiz.Freecam/Freecam.cs /// public class FreeCamera : MonoBehaviour { - public bool IsActive = false; + private CoopPlayer? _playerSpectating; + private bool _isSpectatingPlayer = false; + private bool _spectateRightShoulder = true; + + public bool IsActive { get; set; } = false; - [UsedImplicitly] - public void Update() + private void StopSpectatingPlayer() { - if (!IsActive) + if (_playerSpectating != null) { - return; + _playerSpectating = null; + } + if (transform.parent != null) + { + transform.parent = null; + } + _isSpectatingPlayer = false; + } + + private void SpectateNextPlayer() + { + UpdatePlayerSpectator(true); + } + + private void SpectatePreviousPlayer() + { + UpdatePlayerSpectator(false); + } + + /// + /// Updates the player beign followed by the camera + /// + /// True for the next player and false for the previous player + private void UpdatePlayerSpectator(bool nextPlayer) + { + SITGameComponent coopGameComponent = SITGameComponent.GetCoopGameComponent(); + List players = [.. coopGameComponent + .Players + .Values + .Where(x => !x.IsYourPlayer && x.HealthController.IsAlive && x.GroupId?.Contains("SIT") == true) + ]; + + if (players.Count > 0) + { + if (_playerSpectating == null) + { + if (players[0] != null) + { + _playerSpectating = players[0]; + } + } + else + { + int playerIndex = 0; + if (nextPlayer) + { + // We want to look for the next player in the list + playerIndex = players.IndexOf(_playerSpectating) + 1; + if (playerIndex > players.Count - 1) + { + playerIndex = 0; + } + } + else + { + // We want to find the previous player + playerIndex = players.IndexOf(_playerSpectating) - 1; + if (playerIndex < 0) + { + playerIndex = players.Count - 1; + } + } + + // Update the player we are spectating + _playerSpectating = players[playerIndex]; + } + + if (_playerSpectating != null) + { + _isSpectatingPlayer = true; + + // Attach the camera to the player we are spectating; + SetPlayerSpectateShoulder(); + } + } + else + { + StopSpectatingPlayer(); } + } - var fastMode = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - var movementSpeed = fastMode ? 20f : 3f; + private void MoveAndRotateCamera() + { + bool fastMode = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + float movementSpeed = fastMode ? 20f : 3f; + // Strafe Right if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) { transform.position += (-transform.right * (movementSpeed * Time.deltaTime)); } + // Strafe Left if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) { transform.position += (transform.right * (movementSpeed * Time.deltaTime)); } + // Forwards if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) { transform.position += (transform.forward * (movementSpeed * Time.deltaTime)); } + // Backwards if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) { transform.position += (-transform.forward * (movementSpeed * Time.deltaTime)); } - if (true) + // Up + if (Input.GetKey(KeyCode.Q)) { - if (Input.GetKey(KeyCode.Q)) - { - transform.position += (transform.up * (movementSpeed * Time.deltaTime)); - } + transform.position += (transform.up * (movementSpeed * Time.deltaTime)); + } - if (Input.GetKey(KeyCode.E)) - { - transform.position += (-transform.up * (movementSpeed * Time.deltaTime)); - } + // Down + if (Input.GetKey(KeyCode.E)) + { + transform.position += (-transform.up * (movementSpeed * Time.deltaTime)); + } - if (Input.GetKey(KeyCode.R) || Input.GetKey(KeyCode.PageUp)) - { - transform.position += (Vector3.up * (movementSpeed * Time.deltaTime)); - } + // Up + if (Input.GetKey(KeyCode.R) || Input.GetKey(KeyCode.PageUp)) + { + transform.position += (Vector3.up * (movementSpeed * Time.deltaTime)); + } - if (Input.GetKey(KeyCode.F) || Input.GetKey(KeyCode.PageDown)) - { - transform.position += (-Vector3.up * (movementSpeed * Time.deltaTime)); - } + // Down + if (Input.GetKey(KeyCode.F) || Input.GetKey(KeyCode.PageDown)) + { + transform.position += (-Vector3.up * (movementSpeed * Time.deltaTime)); } float newRotationX = transform.localEulerAngles.y + Input.GetAxis("Mouse X") * 3f; float newRotationY = transform.localEulerAngles.x - Input.GetAxis("Mouse Y") * 3f; transform.localEulerAngles = new Vector3(newRotationY, newRotationX, 0f); + } - //if (FreecamPlugin.CameraMousewheelZoom.Value) - //{ - // float axis = Input.GetAxis("Mouse ScrollWheel"); - // if (axis != 0) - // { - // var zoomSensitivity = fastMode ? FreecamPlugin.CameraFastZoomSpeed.Value : FreecamPlugin.CameraZoomSpeed.Value; - // transform.position += transform.forward * (axis * zoomSensitivity); - // } - //} + private void SetPlayerSpectateShoulder() + { + if (_isSpectatingPlayer) + { + if (_spectateRightShoulder) + { + transform.parent = _playerSpectating?.PlayerBones.RightShoulder.Original; + transform.localEulerAngles = new Vector3(250, 270, 270); + transform.localPosition = new Vector3(-0.12f, 0.04f, 0.16f); + } + else + { + transform.parent = _playerSpectating?.PlayerBones.LeftShoulder.Original; + transform.localEulerAngles = new Vector3(250, 90, 270); + transform.localPosition = new Vector3(-0.12f, -0.04f, -0.16f); + } + } } - [UsedImplicitly] - private void OnDestroy() + protected void OnDestroy() { Destroy(this); } + + protected void Update() + { + if (!IsActive) + { + return; + } + + // Spectate the next player + if (Input.GetKeyDown(KeyCode.Mouse0)) + { + SpectateNextPlayer(); + } + // Spectate the previous player + else if (Input.GetKeyDown(KeyCode.Mouse1)) + { + SpectatePreviousPlayer(); + } + // Stop following the currently selected player + else if (Input.GetKeyDown(KeyCode.End)) + { + StopSpectatingPlayer(); + } + else if (Input.GetKeyDown(KeyCode.Home)) + { + _spectateRightShoulder = !_spectateRightShoulder; + SetPlayerSpectateShoulder(); + } + + // If we aren't spectating anyone then just update the camera normally + if (!_isSpectatingPlayer) + { + MoveAndRotateCamera(); + } + } } } \ No newline at end of file diff --git a/Source/Coop/FreeCamera/FreeCameraController.cs b/Source/Coop/FreeCamera/FreeCameraController.cs index 853caa026..97cfe5277 100644 --- a/Source/Coop/FreeCamera/FreeCameraController.cs +++ b/Source/Coop/FreeCamera/FreeCameraController.cs @@ -1,13 +1,16 @@ -using BSG.CameraEffects; +#nullable enable + +using BepInEx.Logging; +using BSG.CameraEffects; using Comfort.Common; using EFT; using EFT.CameraControl; using EFT.UI; -using HarmonyLib; using StayInTarkov.Configuration; using StayInTarkov.Coop.Components.CoopGameComponents; -using StayInTarkov.Coop.SITGameModes; +using StayInTarkov.Coop.Players; using System; +using System.Collections; using UnityEngine; using UnityStandardAssets.ImageEffects; @@ -20,29 +23,32 @@ namespace StayInTarkov.Coop.FreeCamera public class FreeCameraController : MonoBehaviour { - //private GameObject _mainCamera; - private FreeCamera _freeCamScript; + private FreeCamera? _freeCamScript; - private BattleUIScreen _playerUi; + private BattleUIScreen? _playerUi; private bool _uiHidden; - private GamePlayerOwner _gamePlayerOwner; + private GamePlayerOwner? _gamePlayerOwner; + private DateTime _lastTime = DateTime.MinValue; + + private ManualLogSource Logger { get; } = BepInEx.Logging.Logger.CreateLogSource("FreeCameraController"); + private CoopPlayer Player => (CoopPlayer) Singleton.Instance.MainPlayer; - public GameObject CameraParent { get; set; } - public Camera CameraFreeCamera { get; private set; } - public Camera CameraMain { get; private set; } + public GameObject? CameraParent { get; set; } + public Camera? CameraFreeCamera { get; private set; } + public Camera? CameraMain { get; private set; } - void Awake() + protected void Awake() { CameraParent = new GameObject("CameraParent"); - var FCamera = CameraParent.GetOrAddComponent(); + Camera FCamera = CameraParent.GetOrAddComponent(); FCamera.enabled = false; } public void Start() { // Find Main Camera - CameraMain = FPSCamera.Instance.Camera; + CameraMain = CameraClass.Instance.Camera; if (CameraMain == null) { return; @@ -56,153 +62,139 @@ public void Start() } // Get GamePlayerOwner component - _gamePlayerOwner = GetLocalPlayerFromWorld().GetComponentInChildren(); + _gamePlayerOwner = GetLocalPlayerFromWorld()?.GetComponentInChildren(); if (_gamePlayerOwner == null) { return; } + + Player.OnPlayerDead += Player_OnPlayerDead; } - private DateTime _lastTime = DateTime.MinValue; + private IEnumerator PlayerDeathRoutine() + { + yield return new WaitForSeconds(PluginConfigSettings.Instance?.CoopSettings.BlackScreenOnDeathTime ?? 5); - int DeadTime = 0; + var fpsCamInstance = CameraClass.Instance; + if (fpsCamInstance == null) + { + Logger.LogDebug("fpsCamInstance for camera is null"); + yield break; + } + + // Reset FOV after died + if (fpsCamInstance.Camera != null) + fpsCamInstance.Camera.fieldOfView = Singleton.Instance.Game.Settings.FieldOfView; + + EffectsController effectsController = fpsCamInstance.EffectsController; + if (effectsController == null) + { + Logger.LogDebug("effects controller for camera is null"); + yield break; + } + + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + DisableAndDestroyEffect(effectsController.GetComponent()); + //DisableAndDestroyEffect(effectsController.GetComponent()); + + var ccBlends = fpsCamInstance.EffectsController.GetComponents(); + if (ccBlends != null) + foreach (var ccBlend in ccBlends) + DisableAndDestroyEffect(ccBlend); + + DisableAndDestroyEffect(fpsCamInstance.VisorEffect); + DisableAndDestroyEffect(fpsCamInstance.NightVision); + DisableAndDestroyEffect(fpsCamInstance.ThermalVision); + + // Go to free camera mode + ToggleCamera(); + ToggleUi(); + } + + private void Player_OnPlayerDead(EFT.Player player, IPlayer lastAggressor, DamageInfo damageInfo, EBodyPart part) + { + Player.OnPlayerDead -= Player_OnPlayerDead; + StartCoroutine(PlayerDeathRoutine()); + } public void Update() { if (_gamePlayerOwner == null) return; - if (_gamePlayerOwner.Player == null) + if (Player == null) return; - if (_gamePlayerOwner.Player.PlayerHealthController == null) + if (Player.PlayerHealthController == null) return; - if (!SITGameComponent.TryGetCoopGameComponent(out var coopGC)) + if (!SITGameComponent.TryGetCoopGameComponent(out SITGameComponent coopGC)) return; - var coopGame = coopGC.LocalGameInstance as CoopSITGame; - if (coopGame == null) - return; var quitState = coopGC.GetQuitState(); - - if (_gamePlayerOwner.Player.PlayerHealthController.IsAlive - && (Input.GetKey(KeyCode.F9) || (quitState != SITGameComponent.EQuitState.NONE && !_freeCamScript.IsActive)) - && _lastTime < DateTime.Now.AddSeconds(-3)) + if (Player.PlayerHealthController.IsAlive && + (Input.GetKey(KeyCode.F9) || (quitState != SITGameComponent.EQuitState.NONE && _freeCamScript?.IsActive == false)) && + _lastTime < DateTime.Now.AddSeconds(-3)) { _lastTime = DateTime.Now; ToggleCamera(); ToggleUi(); - } - - if (!_gamePlayerOwner.Player.PlayerHealthController.IsAlive) - { - // This is to make sure the screen effect remove code only get executed once, instead of running every frame. - if (DeadTime == -1) - return; - - if (DeadTime < PluginConfigSettings.Instance.CoopSettings.BlackScreenOnDeathTime) - { - DeadTime++; - } - else - { - DeadTime = -1; - - var fpsCamInstance = FPSCamera.Instance; - if (fpsCamInstance == null) - return; - - // Reset FOV after died - if (fpsCamInstance.Camera != null) - fpsCamInstance.Camera.fieldOfView = Singleton.Instance.Game.Settings.FieldOfView; - - var effectsController = fpsCamInstance.EffectsController; - if (effectsController == null) - return; - - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - DisableAndDestroyEffect(effectsController.GetComponent()); - //DisableAndDestroyEffect(effectsController.GetComponent()); - - var ccBlends = fpsCamInstance.EffectsController.GetComponents(); - if (ccBlends != null) - foreach (var ccBlend in ccBlends) - DisableAndDestroyEffect(ccBlend); - - DisableAndDestroyEffect(fpsCamInstance.VisorEffect); - DisableAndDestroyEffect(fpsCamInstance.NightVision); - DisableAndDestroyEffect(fpsCamInstance.ThermalVision); - - // Go to free camera mode - ToggleCamera(); - ToggleUi(); - } - } + } } - //DateTime? _lastOcclusionCullCheck = null; - //Vector3? _playerDeathOrExitPosition; - //bool showAtDeathOrExitPosition; - /// /// Toggles the Freecam mode /// public void ToggleCamera() { // Get our own Player instance. Null means we're not in a raid - var localPlayer = GetLocalPlayerFromWorld(); - if (localPlayer == null) + if (Player == null) return; - if (!_freeCamScript.IsActive) + if (_freeCamScript?.IsActive == false) { GameObject[] allGameObject = Resources.FindObjectsOfTypeAll(); foreach (GameObject gobj in allGameObject) { - if (gobj.GetComponent() != null) - { - gobj.GetComponent().ForceEnable(true); - } + gobj.GetComponent()?.ForceEnable(true); } - SetPlayerToFreecamMode(localPlayer); + SetPlayerToFreecamMode(Player); } else { - SetPlayerToFirstPersonMode(localPlayer); + SetPlayerToFirstPersonMode(Player); } } /// /// Hides the main UI (health, stamina, stance, hotbar, etc.) /// - private void ToggleUi() + public void ToggleUi() { // Check if we're currently in a raid - if (GetLocalPlayerFromWorld() == null) + if (Player == null) return; // If we don't have the UI Component cached, go look for it in the scene if (_playerUi == null) { - var gameObject = GameObject.Find("BattleUIScreen"); + GameObject gameObject = GameObject.Find("BattleUIScreen"); if (gameObject == null) return; _playerUi = gameObject.GetComponent(); - if (_playerUi == null) { - //FreecamPlugin.Logger.LogError("Failed to locate player UI"); + Logger.LogError("Failed to locate player UI"); return; } } @@ -224,16 +216,20 @@ private void SetPlayerToFreecamMode(EFT.Player localPlayer) // This means our character will be fully visible, while letting the camera move freely localPlayer.PointOfView = EPointOfView.ThirdPerson; - // Get the PlayerBody reference. It's a protected field, so we have to use traverse to fetch it - var playerBody = Traverse.Create(localPlayer).Field("_playerBody").Value; - if (playerBody != null) + if (localPlayer.PlayerBody != null) { - playerBody.PointOfView.Value = EPointOfView.FreeCamera; + localPlayer.PlayerBody.PointOfView.Value = EPointOfView.FreeCamera; localPlayer.GetComponent().UpdatePointOfView(); } - _gamePlayerOwner.enabled = false; - _freeCamScript.IsActive = true; + if (_gamePlayerOwner != null) + { + _gamePlayerOwner.enabled = false; + } + if (_freeCamScript != null) + { + _freeCamScript.IsActive = true; + } } /// @@ -242,19 +238,19 @@ private void SetPlayerToFreecamMode(EFT.Player localPlayer) /// private void SetPlayerToFirstPersonMode(EFT.Player localPlayer) { - _freeCamScript.IsActive = false; - - //if (FreecamPlugin.CameraRememberLastPosition.Value) - //{ - // _lastPosition = _mainCamera.transform.position; - // _lastRotation = _mainCamera.transform.rotation; - //} + if (_freeCamScript != null) + { + _freeCamScript.IsActive = true; + } // re-enable _gamePlayerOwner - _gamePlayerOwner.enabled = true; + if (_gamePlayerOwner != null) + { + _gamePlayerOwner.enabled = false; + } localPlayer.PointOfView = EPointOfView.FirstPerson; - FPSCamera.Instance.SetOcclusionCullingEnabled(true); + CameraClass.Instance.SetOcclusionCullingEnabled(true); } @@ -262,12 +258,14 @@ private void SetPlayerToFirstPersonMode(EFT.Player localPlayer) /// Gets the current instance if it's available /// /// Local instance; returns null if the game is not in raid - private EFT.Player GetLocalPlayerFromWorld() + private EFT.Player? GetLocalPlayerFromWorld() { // If the GameWorld instance is null or has no RegisteredPlayers, it most likely means we're not in a raid - var gameWorld = Singleton.Instance; + GameWorld gameWorld = Singleton.Instance; if (gameWorld == null || gameWorld.MainPlayer == null) + { return null; + } // One of the RegisteredPlayers will have the IsYourPlayer flag set, which will be our own Player instance return gameWorld.MainPlayer; @@ -284,7 +282,7 @@ public void DisableAndDestroyEffect(MonoBehaviour effect) public void OnDestroy() { - GameObject.Destroy(CameraParent); + Destroy(CameraParent); // Destroy FreeCamScript before FreeCamController if exists Destroy(_freeCamScript); diff --git a/Source/Coop/IModuleReplicationWorldPatch.cs b/Source/Coop/IModuleReplicationWorldPatch.cs deleted file mode 100644 index 27b3f0b88..000000000 --- a/Source/Coop/IModuleReplicationWorldPatch.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace StayInTarkov.Coop -{ - internal interface IModuleReplicationWorldPatch - { - public Type InstanceType { get; } - public string MethodName { get; } - public bool DisablePatch { get; } - - public void Replicated(ref Dictionary packet); - - } -} diff --git a/Source/Coop/Matchmaker/MatchmakerAccept/MatchmakerAcceptScreenShowPatch.cs b/Source/Coop/Matchmaker/MatchmakerAccept/MatchmakerAcceptScreenShowPatch.cs index 9d660f910..154aacc56 100644 --- a/Source/Coop/Matchmaker/MatchmakerAccept/MatchmakerAcceptScreenShowPatch.cs +++ b/Source/Coop/Matchmaker/MatchmakerAccept/MatchmakerAcceptScreenShowPatch.cs @@ -29,7 +29,7 @@ protected override MethodBase GetTargetMethod() private static DateTime LastClickedTime { get; set; } = DateTime.MinValue; - private static GameObject MatchmakerObject { get; set; } + public static GameObject MatchmakerObject { get; set; } [PatchPrefix] private static void Pre( @@ -57,33 +57,6 @@ MatchMakerPlayerPreview ____playerModelView sitMatchMaker.OriginalAcceptButton = ____acceptButton; sitMatchMaker.OriginalBackButton = ____backButton; sitMatchMaker.MatchMakerPlayerPreview = ____playerModelView; - - - //var rs = raidSettings; - //____acceptButton.OnClick.AddListener(() => - //{ - // if (LastClickedTime < DateTime.Now.AddSeconds(-10)) - // { - // LastClickedTime = DateTime.Now; - - // //Logger.LogDebug("MatchmakerAcceptScreenShow.PatchPrefix:Clicked"); - // if (MatchmakerAcceptPatches.CheckForMatch(rs, out string returnedJson)) - // { - // Logger.LogDebug(returnedJson); - // JObject result = JObject.Parse(returnedJson); - // var groupId = result["ServerId"].ToString(); - // Matchmaker.MatchmakerAcceptPatches.SetGroupId(groupId); - // MatchmakerAcceptPatches.MatchingType = EMatchmakerType.GroupPlayer; - // GC.Collect(); - // GC.WaitForPendingFinalizers(); - // GC.Collect(); - // } - // else - // { - // MatchmakerAcceptPatches.CreateMatch(MatchmakerAcceptPatches.Profile.AccountId, rs); - // } - // } - //}); } diff --git a/Source/Coop/Matchmaker/MatchmakerAccept/SITMatchmaking.cs b/Source/Coop/Matchmaker/MatchmakerAccept/SITMatchmaking.cs index a31e96f3c..aa18f38e4 100644 --- a/Source/Coop/Matchmaker/MatchmakerAccept/SITMatchmaking.cs +++ b/Source/Coop/Matchmaker/MatchmakerAccept/SITMatchmaking.cs @@ -55,7 +55,7 @@ public static object? MatchmakerScreenController } } - public static MatchmakerTimeHasCome.TimeHasComeScreenController? TimeHasComeScreenController { get; internal set; } + public static TimeHasComeScreenController? TimeHasComeScreenController { get; internal set; } public static ESITProtocol SITProtocol { get; internal set; } = ESITProtocol.RelayTcp; public static string PublicIPAddress { get; internal set; } = ""; public static int PublicPort { get; internal set; } = 6972; @@ -93,72 +93,6 @@ public static void SetTimestamp(long ts) timestamp = ts; } - public static bool CheckForMatch(RaidSettings settings, string password, out string outJson, out string errorMessage) - { - Logger.LogDebug(nameof(CheckForMatch)); - - errorMessage = ((string?)StayInTarkovPlugin.LanguageDictionary["NO-SERVER-MATCH"]) ?? "NO-SERVER-MATCH"; - outJson = string.Empty; - - if (SITMatchmaking.MatchMakerAcceptScreenInstance != null) - { - JObject settingsJSON = JObject.FromObject(settings); - settingsJSON.Add("password", password); - - outJson = AkiBackendCommunication.Instance.PostJson("/coop/server/exist", JsonConvert.SerializeObject(settingsJSON)); - Logger.LogInfo(outJson); - - if (!string.IsNullOrEmpty(outJson)) - { - bool serverExists = false; - if (outJson.Equals("null", StringComparison.OrdinalIgnoreCase)) - { - serverExists = false; - } - else - { - var outJObject = JObject.Parse(outJson); - - if (outJObject.ContainsKey("passwordRequired")) - { - errorMessage = "passwordRequired"; - return false; - } - - if (outJObject.ContainsKey("invalidPassword")) - { - errorMessage = (string?)StayInTarkovPlugin.LanguageDictionary["INVALID-PASSWORD"] ?? "INVALID-PASSWORD"; - return false; - } - - if (outJObject.ContainsKey("gameVersion")) - { - if ((string?)JObject.Parse(outJson)["gameVersion"] != StayInTarkovPlugin.EFTVersionMajor) - { - errorMessage = $"{StayInTarkovPlugin.LanguageDictionary["USE-A-DIFFERENT-VERSION-OF-EFT"]} {StayInTarkovPlugin.EFTVersionMajor} {StayInTarkovPlugin.LanguageDictionary["THAN-SERVER-RUNNING"]} {JObject.Parse(outJson)["gameVersion"]}"; - return false; - } - } - - if (outJObject.ContainsKey("sitVersion")) - { - if ((string?)JObject.Parse(outJson)["sitVersion"] != Assembly.GetExecutingAssembly().GetName().Version.ToString()) - { - errorMessage = $"{StayInTarkovPlugin.LanguageDictionary["USE-A-DIFFERENT-VERSION-OF-SIT"]} {Assembly.GetExecutingAssembly().GetName().Version.ToString()} {StayInTarkovPlugin.LanguageDictionary["THAN-SERVER-RUNNING"]} {JObject.Parse(outJson)["sitVersion"]}"; - return false; - } - } - - serverExists = true; - } - Logger.LogInfo($"CheckForMatch:Server Exists?:{serverExists}"); - - return serverExists; - } - } - return false; - } - public static bool TryJoinMatch(RaidSettings settings, string profileId, string serverId, string password, out string outJson, out string errorMessage) { errorMessage = (string?)StayInTarkovPlugin.LanguageDictionary["NO-SERVER-MATCH"] ?? "NO-SERVER-MATCH"; @@ -172,7 +106,7 @@ public static bool TryJoinMatch(RaidSettings settings, string profileId, string objectToSend.Add("serverId", serverId); objectToSend.Add("password", password); - outJson = AkiBackendCommunication.Instance.PostJson("/coop/server/join", objectToSend.ToJson()); + outJson = AkiBackendCommunication.Instance.PostJsonBLOCKING("/coop/server/join", objectToSend.ToJson()); Logger.LogInfo(outJson); if (!string.IsNullOrEmpty(outJson)) @@ -258,7 +192,8 @@ public static void CreateMatch(string profileId Logger.LogDebug($"{objectToSend.ToJson()}"); // KWJimWails: Set request timeout to 20 seconds, because the Streets of Tarkov need a long time to create loot infos. - string result = AkiBackendCommunication.Instance.PostJson("/coop/server/create", JsonConvert.SerializeObject(objectToSend), true, 20000); + string result = AkiBackendCommunication.Instance.PostJsonBLOCKING( + "/coop/server/create", JsonConvert.SerializeObject(objectToSend), timeout: 20000); if (string.IsNullOrEmpty(result)) { diff --git a/Source/Coop/ModuleReplicationPatch.cs b/Source/Coop/ModuleReplicationPatch.cs index 1f75da701..fd01225c8 100644 --- a/Source/Coop/ModuleReplicationPatch.cs +++ b/Source/Coop/ModuleReplicationPatch.cs @@ -67,8 +67,6 @@ public static T DeserializeObject(string s) return default(T); } - public abstract void Replicated(EFT.Player player, Dictionary dict); - protected static ConcurrentDictionary>> ProcessedCalls = new(); protected static bool HasProcessed(Type type, EFT.Player player, Dictionary dict) @@ -121,15 +119,6 @@ protected static bool HasProcessed(Type type, string playerId, long timestamp) return true; } - public static void Replicate(Type type, EFT.Player player, Dictionary dict) - { - if (!Patches.Any(x => x.GetType().Equals(type))) - return; - - var p = Patches.Single(x => x.GetType().Equals(type)); - p.Value.Replicated(player, dict); - } - public static bool IsHighPingOrAI(EFT.Player player) { if (AkiBackendCommunication.Instance.HighPingMode && player.IsYourPlayer) diff --git a/Source/Coop/NetworkPacket/AI/DespawnAIPacket.cs b/Source/Coop/NetworkPacket/AI/DespawnAIPacket.cs new file mode 100644 index 000000000..a7261a7ac --- /dev/null +++ b/Source/Coop/NetworkPacket/AI/DespawnAIPacket.cs @@ -0,0 +1,48 @@ +using BepInEx.Logging; +using StayInTarkov.Coop.Components.CoopGameComponents; +using System; +using System.IO; + +namespace StayInTarkov.Coop.NetworkPacket.AI +{ + public class DespawnAIPacket : BasePacket + { + public string AIProfileId { get; set; } + + public DespawnAIPacket() : base(nameof(DespawnAIPacket)) + { + } + + public override byte[] Serialize() + { + var ms = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(ms); + WriteHeader(writer); + writer.Write(AIProfileId); + return ms.ToArray(); + } + + public override ISITPacket Deserialize(byte[] bytes) + { + using BinaryReader reader = new BinaryReader(new MemoryStream(bytes)); + ReadHeader(reader); + AIProfileId = reader.ReadString(); + return this; + } + + public override void Process() + { + if (!SITGameComponent.TryGetCoopGameComponent(out var coopGameComponent)) + return; + +#if DEBUG + StayInTarkovHelperConstants.Logger.LogInfo($"{nameof(DespawnAIPacket)}: {AIProfileId}"); +#endif + + if (!coopGameComponent.ProfileIdsAI.Contains(AIProfileId)) + return; + + coopGameComponent.AddToDespawnList(AIProfileId); + } + } +} diff --git a/Source/Coop/NetworkPacket/Airdrop/AirdropBoxPositionSyncPacket.cs b/Source/Coop/NetworkPacket/Airdrop/AirdropBoxPositionSyncPacket.cs new file mode 100644 index 000000000..956d4b87d --- /dev/null +++ b/Source/Coop/NetworkPacket/Airdrop/AirdropBoxPositionSyncPacket.cs @@ -0,0 +1,60 @@ +using Aki.Custom.Airdrops; +using BepInEx.Logging; +using Comfort.Common; +using StayInTarkov.Coop.Matchmaker; +using System.IO; +using UnityEngine; +using static StayInTarkov.Networking.SITSerialization; + +namespace StayInTarkov.Coop.NetworkPacket.Airdrop +{ + public sealed class AirdropBoxPositionSyncPacket : BasePacket + { + static AirdropBoxPositionSyncPacket() + { + Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(AirdropPacket)); + } + + public AirdropBoxPositionSyncPacket() : base(nameof(AirdropBoxPositionSyncPacket)) + { + } + + public Vector3 Position { get; set; } + public static ManualLogSource Logger { get; } + + public override byte[] Serialize() + { + var ms = new MemoryStream(); + using BinaryWriter writer = new(ms); + WriteHeader(writer); + Vector3Utils.Serialize(writer, this.Position); + return ms.ToArray(); + } + + public override ISITPacket Deserialize(byte[] bytes) + { + using BinaryReader reader = new(new MemoryStream(bytes)); + ReadHeader(reader); + Position = Vector3Utils.Deserialize(reader); + return this; + } + + public override void Process() + { + if (!SITMatchmaking.IsClient) + return; + + if (!Singleton.Instantiated) + { + Logger.LogError($"{nameof(SITAirdropsManager)} has not been instantiated!"); + return; + } + + + //Logger.LogDebug($"{nameof(Process)}"); + + Singleton.Instance.AirdropBox.ClientSyncPosition = Position; + } + + } +} diff --git a/Source/Coop/NetworkPacket/Airdrop/AirdropLootPacket.cs b/Source/Coop/NetworkPacket/Airdrop/AirdropLootPacket.cs index 8c285cd90..3af47fc16 100644 --- a/Source/Coop/NetworkPacket/Airdrop/AirdropLootPacket.cs +++ b/Source/Coop/NetworkPacket/Airdrop/AirdropLootPacket.cs @@ -1,33 +1,41 @@ -using Aki.Custom.Airdrops.Models; -using StayInTarkov.AkiSupport.Airdrops.Models; -using System; -using System.Collections.Generic; +using Aki.Custom.Airdrops; +using Aki.Custom.Airdrops.Models; +using BepInEx.Logging; +using Comfort.Common; +using StayInTarkov.Coop.Matchmaker; +using System.Collections; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using UnityEngine; namespace StayInTarkov.Coop.NetworkPacket.Airdrop { public sealed class AirdropLootPacket : BasePacket { + + static AirdropLootPacket() + { + Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(AirdropLootPacket)); + } + + private static ManualLogSource Logger { get; set; } + public string AirdropLootResultModelJson { get; set; } public string AirdropConfigModelJson { get; set; } - public AirdropLootPacket(AirdropLootResultModel airdropLootResultModel, AirdropConfigModel airdropConfigModel) : base("AirdropLootPacket") + public AirdropLootPacket(AirdropLootResultModel airdropLootResultModel, AirdropConfigModel airdropConfigModel) : base(nameof(AirdropLootPacket)) { AirdropLootResultModelJson = airdropLootResultModel.SITToJson(); AirdropConfigModelJson = airdropConfigModel.SITToJson(); } - public AirdropLootPacket() : base("AirdropLootPacket") + public AirdropLootPacket() : base(nameof(AirdropLootPacket)) { } public override byte[] Serialize() { var ms = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(ms); + using BinaryWriter writer = new(ms); WriteHeader(writer); writer.Write(AirdropLootResultModelJson); writer.Write(AirdropConfigModelJson); @@ -36,11 +44,54 @@ public override byte[] Serialize() public override ISITPacket Deserialize(byte[] bytes) { - using BinaryReader reader = new BinaryReader(new MemoryStream(bytes)); + using BinaryReader reader = new(new MemoryStream(bytes)); ReadHeader(reader); AirdropLootResultModelJson = reader.ReadString(); AirdropConfigModelJson = reader.ReadString(); return this; } + + public override void Process() + { + if (!SITMatchmaking.IsClient) + return; + + if (!Singleton.Instantiated) + { + Logger.LogDebug($"{nameof(SITAirdropsManager)} has not been instantiated! Waiting..."); + StayInTarkovPlugin.Instance.StartCoroutine(AirdropLootPacketWaitAndProcess()); + return; + } + + + if (!this.AirdropLootResultModelJson.TrySITParseJson(out var airdropLootResultModel)) + { + Logger.LogError($"{nameof(AirdropLootResultModel)} failed to deserialize!"); + return; + } + + if (!this.AirdropConfigModelJson.TrySITParseJson(out var airdropConfigModel)) + { + Logger.LogError($"{nameof(AirdropConfigModel)} failed to deserialize!"); + return; + } + + + Singleton.Instance.ReceiveBuildLootContainer( + airdropLootResultModel, + airdropConfigModel + ); + + + } + + private IEnumerator AirdropLootPacketWaitAndProcess() + { + var waitForSec = new WaitForSeconds(5); + while (!Singleton.Instantiated) + yield return waitForSec; + + Process(); + } } } diff --git a/Source/Coop/NetworkPacket/Airdrop/AirdropPacket.cs b/Source/Coop/NetworkPacket/Airdrop/AirdropPacket.cs index 2055e6796..3b360eb03 100644 --- a/Source/Coop/NetworkPacket/Airdrop/AirdropPacket.cs +++ b/Source/Coop/NetworkPacket/Airdrop/AirdropPacket.cs @@ -1,30 +1,36 @@ -using StayInTarkov.AkiSupport.Airdrops.Models; -using System; -using System.Collections.Generic; +using Aki.Custom.Airdrops; +using BepInEx.Logging; +using Comfort.Common; +using StayInTarkov.AkiSupport.Airdrops.Models; +using StayInTarkov.Coop.Matchmaker; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace StayInTarkov.Coop.NetworkPacket.Airdrop { public sealed class AirdropPacket : BasePacket { + static AirdropPacket() + { + Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(AirdropPacket)); + } + + private static ManualLogSource Logger; + public string AirdropParametersModelJson { get; set; } - public AirdropPacket(AirdropParametersModel airdropParametersModel) : base("AirdropPacket") + public AirdropPacket(AirdropParametersModel airdropParametersModel) : base(nameof(AirdropPacket)) { AirdropParametersModelJson = airdropParametersModel.SITToJson(); } - public AirdropPacket() : base("AirdropPacket") + public AirdropPacket() : base(nameof(AirdropPacket)) { } public override byte[] Serialize() { var ms = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(ms); + using BinaryWriter writer = new(ms); WriteHeader(writer); writer.Write(AirdropParametersModelJson); return ms.ToArray(); @@ -32,10 +38,30 @@ public override byte[] Serialize() public override ISITPacket Deserialize(byte[] bytes) { - using BinaryReader reader = new BinaryReader(new MemoryStream(bytes)); + using BinaryReader reader = new(new MemoryStream(bytes)); ReadHeader(reader); AirdropParametersModelJson = reader.ReadString(); return this; } + + public override void Process() + { + if (!SITMatchmaking.IsClient) + return; + + if (!Singleton.Instantiated) + { + Logger.LogError($"{nameof(SITAirdropsManager)} has not been instantiated!"); + return; + } + + Singleton.Instance.AirdropParameters = this.AirdropParametersModelJson.SITParseJson(); + +#if DEBUG + Logger.LogDebug($"{nameof(SITAirdropsManager)}{nameof(Singleton.Instance.AirdropParameters)}"); + Logger.LogDebug($"{Singleton.Instance.AirdropParameters.SITToJson()}"); +#endif + + } } } diff --git a/Source/Coop/NetworkPacket/BasePacket.cs b/Source/Coop/NetworkPacket/BasePacket.cs index 557987ff2..b187c53a1 100644 --- a/Source/Coop/NetworkPacket/BasePacket.cs +++ b/Source/Coop/NetworkPacket/BasePacket.cs @@ -49,9 +49,6 @@ public TimeSpan GetTimeSinceSent() return TimeSpan.Zero; } - //[JsonProperty(PropertyName = "pong")] - //public virtual string Pong { get; set; } = DateTime.UtcNow.Ticks.ToString("G"); - public BasePacket(string method) { Method = method; @@ -91,16 +88,6 @@ public virtual void WriteHeader(BinaryWriter writer) writer.WriteNonPrefixedString("?"); } - //public virtual void WriteHeader(NetworkWriter writer) - //{ - // // Prefix SIT - // writer.Write("SIT"); - // // 0.14 is 24 profile Id - // writer.Write(ServerId); - // writer.Write(Method); - // writer.Write("?"); - //} - public virtual void ReadHeader(BinaryReader reader) { if (reader == null) @@ -123,89 +110,6 @@ public virtual void ReadHeader(BinaryReader reader) reader.BaseStream.Position -= 1; } - //public byte[] AutoSerialize() - //{ - // if (string.IsNullOrEmpty(ServerId)) - // { - // throw new ArgumentNullException($"{GetType()}:{nameof(ServerId)}"); - // } - - // if (string.IsNullOrEmpty(Method)) - // { - // throw new ArgumentNullException($"{GetType()}:{nameof(Method)}"); - // } - - // byte[] result = null; - // BinaryWriter binaryWriter = new(new MemoryStream()); - // WriteHeader(binaryWriter); - - // var allPropsFiltered = GetPropertyInfos(this); - // if (allPropsFiltered == null) - // return null; - - // // Extremely useful tool for discovering what is calling something else... - // //#if DEBUG - // // System.Diagnostics.StackTrace t = new System.Diagnostics.StackTrace(true); - // // StayInTarkovHelperConstants.Logger.LogInfo($"{t.ToString()}"); - // //#endif - - - // for (var i = 0; i < allPropsFiltered.Count(); i++) - // { - // var prop = allPropsFiltered[i]; - // var propValue = prop.GetValue(this); - // //StayInTarkovHelperConstants.Logger.LogInfo($"{prop.Name} is {propValue}"); - - // // Process an Array type - // if (prop.PropertyType.IsArray) - // { - // Array array = (Array)propValue; - // binaryWriter.Write(prop.PropertyType.FullName); - // binaryWriter.Write(array.Length); - - // foreach (var item in array) - // { - // if (item.GetType().GetInterface(nameof(ISITPacket)) != null) - // { - // var serializedSITPacket = ((ISITPacket)item).Serialize(); - // binaryWriter.Write((int)serializedSITPacket.Length); - // binaryWriter.Write(serializedSITPacket); - // } - // else - // binaryWriter.Write(item.ToString()); - // } - - // } - // // Process an ISITPacket - // else if (prop.PropertyType.GetInterface(nameof(ISITPacket)) != null) - // { - // binaryWriter.Write(prop.PropertyType.FullName); - // var serializedSITPacket = ((ISITPacket)propValue).Serialize(); - // binaryWriter.Write(serializedSITPacket.Length); - // binaryWriter.Write(serializedSITPacket); - // } - // else if (prop.PropertyType == typeof(bool)) - // { - // binaryWriter.Write((bool)propValue); - // } - // else - // { - // binaryWriter.Write(propValue.ToString()); - // } - - // } - - // if (binaryWriter != null) - // { - // result = ((MemoryStream)binaryWriter.BaseStream).ToArray(); - // binaryWriter.Close(); - // binaryWriter.Dispose(); - // binaryWriter = null; - // } - - // return result; - //} - /// /// Serializes the BasePacket header. All inherited Packet classes must override this. /// @@ -229,227 +133,6 @@ public virtual ISITPacket Deserialize(byte[] bytes) return this; } - //public virtual ISITPacket AutoDeserialize(byte[] data) - //{ - // //StayInTarkovHelperConstants.Logger.LogInfo($"{nameof(AutoDeserialize)}:{Encoding.UTF8.GetString(data)}"); - - // using BinaryReader reader = new BinaryReader(new MemoryStream(data)); - // var headerExists = Encoding.UTF8.GetString(reader.ReadBytes(3)) == "SIT"; - // reader.BaseStream.Position = 0; - // if (headerExists) - // ReadHeader(reader); - - // //StayInTarkovHelperConstants.Logger.LogInfo(nameof(AutoDeserialize)); - // //StayInTarkovHelperConstants.Logger.LogInfo($"headerExists:{headerExists}"); - // //StayInTarkovHelperConstants.Logger.LogInfo($"reader:Position:{reader.BaseStream.Position}"); - // //StayInTarkovHelperConstants.Logger.LogInfo($"reader:Length:{reader.BaseStream.Length}"); - // //StayInTarkovHelperConstants.Logger.LogInfo(this.ToJson()); - - // DeserializePacketIntoObj(reader); - - // //StayInTarkovHelperConstants.Logger.LogInfo(obj.ToJson()); - - // return this; - //} - - //public ISITPacket DeserializePacketSIT(string serializedPacket) - //{ - // AutoDeserialize(Encoding.UTF8.GetBytes(serializedPacket)); - // return this; - //} - - //public ISITPacket DeserializePacketSIT(byte[] data) - //{ - // AutoDeserialize(data); - // return this; - //} - - private void DeserializePacketIntoObj(BinaryReader reader) - { - if (reader == null) - throw new ArgumentNullException(nameof(reader)); - - if (reader.BaseStream.Position >= reader.BaseStream.Length) - return; - - try - { - var allPropsFiltered = GetPropertyInfos(this); - if (allPropsFiltered == null) - return; - - foreach (var prop in allPropsFiltered) - { - if (reader.BaseStream.Position >= reader.BaseStream.Length) - return; - - //StayInTarkovHelperConstants.Logger.LogInfo($"{nameof(DeserializePacketIntoObj)}"); - //StayInTarkovHelperConstants.Logger.LogInfo($"reader:Position:{reader.BaseStream.Position}"); - //StayInTarkovHelperConstants.Logger.LogInfo($"reader:Length:{reader.BaseStream.Length}"); - //StayInTarkovHelperConstants.Logger.LogInfo($"reader:Reading Prop:{prop.Name}"); - - - // Is an array - if (prop.PropertyType.IsArray) - { - //StayInTarkovHelperConstants.Logger.LogDebug($"{prop.Name} is Array"); - var arrayType = reader.ReadString().Replace("[", "").Replace("]", ""); - var arrayCount = reader.ReadInt32(); - //StayInTarkovHelperConstants.Logger.LogDebug($"{arrayType}"); - - // Find Array Type to Instantiate - var arrayTypeToInstantiate = StayInTarkovHelperConstants - .SITTypes - .Union(ReflectionHelpers.EftTypes) - .FirstOrDefault(x => x.FullName == arrayType); - if (arrayTypeToInstantiate == null) - { - StayInTarkovHelperConstants.Logger.LogError($"Failed to find: {arrayTypeToInstantiate}"); - continue; - } - //StayInTarkovHelperConstants.Logger.LogDebug($"{arrayTypeToInstantiate}"); - Array array = Array.CreateInstance(arrayTypeToInstantiate, arrayCount); - if (array == null) - { - StayInTarkovHelperConstants.Logger.LogError($"Failed to create array: {arrayTypeToInstantiate}"); - continue; - } - - for (var i = 0; i < arrayCount; i++) - { - if (arrayTypeToInstantiate.GetInterface(nameof(ISITPacket)) != null) - { - var packetType = reader.ReadString(); - var packetByteLength = reader.ReadInt32(); - StayInTarkovHelperConstants.Logger.LogDebug($"{packetByteLength}"); - var packetBytes = reader.ReadBytes(packetByteLength); - - //StayInTarkovHelperConstants.Logger.LogDebug($"{arrayTypeToInstantiate}"); - //StayInTarkovHelperConstants.Logger.LogDebug($"{Encoding.UTF8.GetString(packetBytes)}"); - //File.WriteAllBytes("t.bin", packetBytes); - - //var sitPacket = Activator.CreateInstance(arrayTypeToInstantiate); - //DeserializePacketSIT(sitPacket, packetBytes); - packetBytes = null; - } - else - { - - } - } - //array = null; - - continue; - } - // Is a SITPacket - else if (prop.PropertyType.GetInterface(nameof(ISITPacket)) != null) - { - //StayInTarkovHelperConstants.Logger.LogDebug($"{prop.Name} is SIT Packet"); - - var packetType = reader.ReadString(); - //StayInTarkovHelperConstants.Logger.LogDebug($"{packetType}"); - var packetByteLength = reader.ReadInt32(); - //StayInTarkovHelperConstants.Logger.LogDebug($"{packetByteLength}"); - var packetBytes = reader.ReadBytes(packetByteLength); - - var typeToInstantiate = StayInTarkovHelperConstants.SITTypes.FirstOrDefault(x => x.FullName == packetType); - if (typeToInstantiate != null) - { - var sitPacket = (BasePacket)Activator.CreateInstance(typeToInstantiate); - //StayInTarkovHelperConstants.Logger.LogDebug($"{prop.Name} is {Encoding.UTF8.GetString(packetBytes)}"); - //sitPacket.DeserializePacketSIT(packetBytes); - //StayInTarkovHelperConstants.Logger.LogDebug($"{prop.Name} is SIT Packet and Deserialized"); - } - - packetBytes = null; - continue; - } - else if (prop.PropertyType == typeof(bool)) - { - prop.SetValue(this, reader.ReadBoolean()); - continue; - } - - - //StayInTarkovHelperConstants.Logger.LogDebug($"{prop.Name}"); - var readString = reader.ReadString(); - //StayInTarkovHelperConstants.Logger.LogDebug($"{prop.Name} to {readString}"); - - switch (prop.PropertyType.Name) - { - case "Float": - prop.SetValue(this, float.Parse(readString)); - break; - case "Single": - prop.SetValue(this, Single.Parse(readString)); - break; - //case "Boolean": - // prop.SetValue(obj, Boolean.Parse(readString)); - // break; - case "String": - prop.SetValue(this, readString); - break; - case "Integer": - case "Int": - case "Int32": - prop.SetValue(this, int.Parse(readString)); - break; - case "Double": - prop.SetValue(this, double.Parse(readString)); - break; - case "Byte": - prop.SetValue(this, byte.Parse(readString)); - break; - default: - - // Process an Enum - if (prop.PropertyType.IsEnum) - prop.SetValue(this, Enum.Parse(prop.PropertyType, readString)); - - - else - { - var jobj = JObject.Parse(readString); - var instance = jobj.ToObject(prop.PropertyType); - prop.SetValue(this, instance); - //StayInTarkovHelperConstants.Logger.LogError($"{prop.Name} of type {prop.PropertyType.Name} could not be parsed by SIT Deserializer!"); - } - break; - } - } - } - finally - { - reader.Close(); - reader.Dispose(); - reader = null; - } - - } - - public Dictionary ToDictionary(byte[] data) - { - Deserialize(data); - - // create a json obj from this obj - var obj = JObject.FromObject(this); - - // read the header to discover SIT/ServerId/Method - using BinaryReader reader = new BinaryReader(new MemoryStream(data)); - var headerExists = Encoding.UTF8.GetString(reader.ReadBytes(3)) == "SIT"; - reader.BaseStream.Position = 0; - if (headerExists) - ReadHeader(reader); - - if (!obj.ContainsKey("data")) - obj.Add("data", data); - - if (!obj.ContainsKey("m")) - obj.Add("m", Method); - - return JsonConvert.DeserializeObject>(obj.SITToJson()); - } - public override string ToString() { return Encoding.UTF8.GetString(Serialize()); @@ -478,13 +161,6 @@ protected virtual void Dispose(bool disposing) } } - // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~BasePacket() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } - public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method diff --git a/Source/Coop/NetworkPacket/FlatBuffers/Extensions.cs b/Source/Coop/NetworkPacket/FlatBuffers/Extensions.cs new file mode 100644 index 000000000..01d61194d --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/Extensions.cs @@ -0,0 +1,18 @@ +using Google.FlatBuffers; +using StayInTarkov.FlatBuffers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace StayInTarkov.Coop.NetworkPacket.FlatBuffers +{ + public static class Extensions + { + public static bool Empty(this PlayerState x) => x.ByteBuffer == null; + public static Vector2 Unity(this Vec2 x) => new(x.X, x.Y); + public static Vector3 Unity(this Vec3 x) => new(x.X, x.Y, x.Z); + } +} diff --git a/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/AnyPacket.cs b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/AnyPacket.cs new file mode 100644 index 000000000..9c371fc32 --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/AnyPacket.cs @@ -0,0 +1,15 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace StayInTarkov.FlatBuffers +{ + +public enum AnyPacket : byte +{ + NONE = 0, + player_state = 1, +}; + + +} diff --git a/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/BodyPartHealth.cs b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/BodyPartHealth.cs new file mode 100644 index 000000000..627455ec3 --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/BodyPartHealth.cs @@ -0,0 +1,31 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace StayInTarkov.FlatBuffers +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct BodyPartHealth : IFlatbufferObject +{ + private Struct __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public void __init(int _i, ByteBuffer _bb) { __p = new Struct(_i, _bb); } + public BodyPartHealth __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public float Current { get { return __p.bb.GetFloat(__p.bb_pos + 0); } } + public float Maximum { get { return __p.bb.GetFloat(__p.bb_pos + 4); } } + + public static Offset CreateBodyPartHealth(FlatBufferBuilder builder, float Current, float Maximum) { + builder.Prep(4, 8); + builder.PutFloat(Maximum); + builder.PutFloat(Current); + return new Offset(builder.Offset); + } +} + + +} diff --git a/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/BodyPartsHealth.cs b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/BodyPartsHealth.cs new file mode 100644 index 000000000..060dcb2ca --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/BodyPartsHealth.cs @@ -0,0 +1,33 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace StayInTarkov.FlatBuffers +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct BodyPartsHealth : IFlatbufferObject +{ + private Struct __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public void __init(int _i, ByteBuffer _bb) { __p = new Struct(_i, _bb); } + public BodyPartsHealth __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public StayInTarkov.FlatBuffers.BodyPartHealth Parts(int j) { return (new StayInTarkov.FlatBuffers.BodyPartHealth()).__assign(__p.bb_pos + 0 + j * 8, __p.bb); } + + public static Offset CreateBodyPartsHealth(FlatBufferBuilder builder, float[] parts_Current, float[] parts_Maximum) { + builder.Prep(4, 64); + for (int _idx0 = 8; _idx0 > 0; _idx0--) { + builder.Prep(4, 8); + builder.PutFloat(parts_Maximum[_idx0-1]); + builder.PutFloat(parts_Current[_idx0-1]); + } + return new Offset(builder.Offset); + } +} + + +} diff --git a/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/EBodyPart.cs b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/EBodyPart.cs new file mode 100644 index 000000000..b14a2d428 --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/EBodyPart.cs @@ -0,0 +1,21 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace StayInTarkov.FlatBuffers +{ + +public enum EBodyPart : byte +{ + Head = 0, + Chest = 1, + Stomach = 2, + LeftArm = 3, + RightArm = 4, + LeftLeg = 5, + RightLeg = 6, + Common = 7, +}; + + +} diff --git a/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/EPlayerState.cs b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/EPlayerState.cs new file mode 100644 index 000000000..3960ebd07 --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/EPlayerState.cs @@ -0,0 +1,42 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace StayInTarkov.FlatBuffers +{ + +public enum EPlayerState : byte +{ + None = 0, + Idle = 1, + ProneIdle = 2, + ProneMove = 3, + Run = 4, + Sprint = 5, + Jump = 6, + FallDown = 7, + Transition = 8, + BreachDoor = 9, + Loot = 10, + Pickup = 11, + Open = 12, + Close = 13, + Unlock = 14, + Sidestep = 15, + DoorInteraction = 16, + Approach = 17, + Prone2Stand = 18, + Transit2Prone = 19, + Plant = 20, + Stationary = 21, + Roll = 22, + JumpLanding = 23, + ClimbOver = 24, + ClimbUp = 25, + VaultingFallDown = 26, + VaultingLanding = 27, + BlindFire = 28, +}; + + +} diff --git a/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/Packet.cs b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/Packet.cs new file mode 100644 index 000000000..8a029a449 --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/Packet.cs @@ -0,0 +1,59 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace StayInTarkov.FlatBuffers +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct Packet : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_23_5_26(); } + public static Packet GetRootAsPacket(ByteBuffer _bb) { return GetRootAsPacket(_bb, new Packet()); } + public static Packet GetRootAsPacket(ByteBuffer _bb, Packet obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public static bool VerifyPacket(ByteBuffer _bb) {Google.FlatBuffers.Verifier verifier = new Google.FlatBuffers.Verifier(_bb); return verifier.VerifyBuffer("", false, PacketVerify.Verify); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public Packet __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public StayInTarkov.FlatBuffers.AnyPacket PacketType { get { int o = __p.__offset(4); return o != 0 ? (StayInTarkov.FlatBuffers.AnyPacket)__p.bb.Get(o + __p.bb_pos) : StayInTarkov.FlatBuffers.AnyPacket.NONE; } } + public TTable? Packet_() where TTable : struct, IFlatbufferObject { int o = __p.__offset(6); return o != 0 ? (TTable?)__p.__union(o + __p.bb_pos) : null; } + public StayInTarkov.FlatBuffers.PlayerState Packet_Asplayer_state() { return Packet_().Value; } + + public static Offset CreatePacket(FlatBufferBuilder builder, + StayInTarkov.FlatBuffers.AnyPacket packet_type = StayInTarkov.FlatBuffers.AnyPacket.NONE, + int packetOffset = 0) { + builder.StartTable(2); + Packet.AddPacket(builder, packetOffset); + Packet.AddPacketType(builder, packet_type); + return Packet.EndPacket(builder); + } + + public static void StartPacket(FlatBufferBuilder builder) { builder.StartTable(2); } + public static void AddPacketType(FlatBufferBuilder builder, StayInTarkov.FlatBuffers.AnyPacket packetType) { builder.AddByte(0, (byte)packetType, 0); } + public static void AddPacket(FlatBufferBuilder builder, int packetOffset) { builder.AddOffset(1, packetOffset, 0); } + public static Offset EndPacket(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } + public static void FinishPacketBuffer(FlatBufferBuilder builder, Offset offset) { builder.Finish(offset.Value); } + public static void FinishSizePrefixedPacketBuffer(FlatBufferBuilder builder, Offset offset) { builder.FinishSizePrefixed(offset.Value); } +} + + +static public class PacketVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyField(tablePos, 4 /*PacketType*/, 1 /*StayInTarkov.FlatBuffers.AnyPacket*/, 1, false) + // && verifier.VerifyUnion(tablePos, 4, 6 /*Packet*/, StayInTarkov.FlatBuffers.AnyPacketVerify.Verify, false) + && verifier.VerifyTableEnd(tablePos); + } +} + +} diff --git a/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/PlayerState.cs b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/PlayerState.cs new file mode 100644 index 000000000..6df0248a1 --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/PlayerState.cs @@ -0,0 +1,124 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace StayInTarkov.FlatBuffers +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct PlayerState : IFlatbufferObject +{ + private Table __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_23_5_26(); } + public static PlayerState GetRootAsPlayerState(ByteBuffer _bb) { return GetRootAsPlayerState(_bb, new PlayerState()); } + public static PlayerState GetRootAsPlayerState(ByteBuffer _bb, PlayerState obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } + public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); } + public PlayerState __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public string ProfileId { get { int o = __p.__offset(4); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } } +#if ENABLE_SPAN_T + public Span GetProfileIdBytes() { return __p.__vector_as_span(4, 1); } +#else + public ArraySegment? GetProfileIdBytes() { return __p.__vector_as_arraysegment(4); } +#endif + public byte[] GetProfileIdArray() { return __p.__vector_as_array(4); } + public StayInTarkov.FlatBuffers.EPlayerState State { get { int o = __p.__offset(6); return o != 0 ? (StayInTarkov.FlatBuffers.EPlayerState)__p.bb.Get(o + __p.bb_pos) : StayInTarkov.FlatBuffers.EPlayerState.None; } } + public float Tilt { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public sbyte Step { get { int o = __p.__offset(10); return o != 0 ? __p.bb.GetSbyte(o + __p.bb_pos) : (sbyte)0; } } + public byte AnimatorStateIndex { get { int o = __p.__offset(12); return o != 0 ? __p.bb.Get(o + __p.bb_pos) : (byte)0; } } + public float CharacterMovementSpeed { get { int o = __p.__offset(14); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float PoseLevel { get { int o = __p.__offset(16); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public bool IsProne { get { int o = __p.__offset(18); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool)false; } } + public bool IsSprinting { get { int o = __p.__offset(20); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool)false; } } + public bool HandsExhausted { get { int o = __p.__offset(22); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool)false; } } + public bool OxygenExhausted { get { int o = __p.__offset(24); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool)false; } } + public bool StaminaExhausted { get { int o = __p.__offset(26); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool)false; } } + public StayInTarkov.FlatBuffers.Vec3? Position { get { int o = __p.__offset(28); return o != 0 ? (StayInTarkov.FlatBuffers.Vec3?)(new StayInTarkov.FlatBuffers.Vec3()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public StayInTarkov.FlatBuffers.Vec2? Rotation { get { int o = __p.__offset(30); return o != 0 ? (StayInTarkov.FlatBuffers.Vec2?)(new StayInTarkov.FlatBuffers.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public StayInTarkov.FlatBuffers.Vec3? HeadRotation { get { int o = __p.__offset(32); return o != 0 ? (StayInTarkov.FlatBuffers.Vec3?)(new StayInTarkov.FlatBuffers.Vec3()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public StayInTarkov.FlatBuffers.Vec2? MovementDirection { get { int o = __p.__offset(34); return o != 0 ? (StayInTarkov.FlatBuffers.Vec2?)(new StayInTarkov.FlatBuffers.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public StayInTarkov.FlatBuffers.Vec2? InputDirection { get { int o = __p.__offset(36); return o != 0 ? (StayInTarkov.FlatBuffers.Vec2?)(new StayInTarkov.FlatBuffers.Vec2()).__assign(o + __p.bb_pos, __p.bb) : null; } } + public sbyte Blindfire { get { int o = __p.__offset(38); return o != 0 ? __p.bb.GetSbyte(o + __p.bb_pos) : (sbyte)0; } } + public float LinearSpeed { get { int o = __p.__offset(40); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public bool LeftStance { get { int o = __p.__offset(42); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool)false; } } + public bool IsAlive { get { int o = __p.__offset(44); return o != 0 ? 0!=__p.bb.Get(o + __p.bb_pos) : (bool)false; } } + public float Energy { get { int o = __p.__offset(46); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float Hydration { get { int o = __p.__offset(48); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float Radiation { get { int o = __p.__offset(50); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public float Poison { get { int o = __p.__offset(52); return o != 0 ? __p.bb.GetFloat(o + __p.bb_pos) : (float)0.0f; } } + public StayInTarkov.FlatBuffers.BodyPartsHealth? BodyPartsHealth { get { int o = __p.__offset(54); return o != 0 ? (StayInTarkov.FlatBuffers.BodyPartsHealth?)(new StayInTarkov.FlatBuffers.BodyPartsHealth()).__assign(o + __p.bb_pos, __p.bb) : null; } } + + public static void StartPlayerState(FlatBufferBuilder builder) { builder.StartTable(26); } + public static void AddProfileId(FlatBufferBuilder builder, StringOffset profileIdOffset) { builder.AddOffset(0, profileIdOffset.Value, 0); } + public static void AddState(FlatBufferBuilder builder, StayInTarkov.FlatBuffers.EPlayerState state) { builder.AddByte(1, (byte)state, 0); } + public static void AddTilt(FlatBufferBuilder builder, float tilt) { builder.AddFloat(2, tilt, 0.0f); } + public static void AddStep(FlatBufferBuilder builder, sbyte step) { builder.AddSbyte(3, step, 0); } + public static void AddAnimatorStateIndex(FlatBufferBuilder builder, byte animatorStateIndex) { builder.AddByte(4, animatorStateIndex, 0); } + public static void AddCharacterMovementSpeed(FlatBufferBuilder builder, float characterMovementSpeed) { builder.AddFloat(5, characterMovementSpeed, 0.0f); } + public static void AddPoseLevel(FlatBufferBuilder builder, float poseLevel) { builder.AddFloat(6, poseLevel, 0.0f); } + public static void AddIsProne(FlatBufferBuilder builder, bool isProne) { builder.AddBool(7, isProne, false); } + public static void AddIsSprinting(FlatBufferBuilder builder, bool isSprinting) { builder.AddBool(8, isSprinting, false); } + public static void AddHandsExhausted(FlatBufferBuilder builder, bool handsExhausted) { builder.AddBool(9, handsExhausted, false); } + public static void AddOxygenExhausted(FlatBufferBuilder builder, bool oxygenExhausted) { builder.AddBool(10, oxygenExhausted, false); } + public static void AddStaminaExhausted(FlatBufferBuilder builder, bool staminaExhausted) { builder.AddBool(11, staminaExhausted, false); } + public static void AddPosition(FlatBufferBuilder builder, Offset positionOffset) { builder.AddStruct(12, positionOffset.Value, 0); } + public static void AddRotation(FlatBufferBuilder builder, Offset rotationOffset) { builder.AddStruct(13, rotationOffset.Value, 0); } + public static void AddHeadRotation(FlatBufferBuilder builder, Offset headRotationOffset) { builder.AddStruct(14, headRotationOffset.Value, 0); } + public static void AddMovementDirection(FlatBufferBuilder builder, Offset movementDirectionOffset) { builder.AddStruct(15, movementDirectionOffset.Value, 0); } + public static void AddInputDirection(FlatBufferBuilder builder, Offset inputDirectionOffset) { builder.AddStruct(16, inputDirectionOffset.Value, 0); } + public static void AddBlindfire(FlatBufferBuilder builder, sbyte blindfire) { builder.AddSbyte(17, blindfire, 0); } + public static void AddLinearSpeed(FlatBufferBuilder builder, float linearSpeed) { builder.AddFloat(18, linearSpeed, 0.0f); } + public static void AddLeftStance(FlatBufferBuilder builder, bool leftStance) { builder.AddBool(19, leftStance, false); } + public static void AddIsAlive(FlatBufferBuilder builder, bool isAlive) { builder.AddBool(20, isAlive, false); } + public static void AddEnergy(FlatBufferBuilder builder, float energy) { builder.AddFloat(21, energy, 0.0f); } + public static void AddHydration(FlatBufferBuilder builder, float hydration) { builder.AddFloat(22, hydration, 0.0f); } + public static void AddRadiation(FlatBufferBuilder builder, float radiation) { builder.AddFloat(23, radiation, 0.0f); } + public static void AddPoison(FlatBufferBuilder builder, float poison) { builder.AddFloat(24, poison, 0.0f); } + public static void AddBodyPartsHealth(FlatBufferBuilder builder, Offset bodyPartsHealthOffset) { builder.AddStruct(25, bodyPartsHealthOffset.Value, 0); } + public static Offset EndPlayerState(FlatBufferBuilder builder) { + int o = builder.EndTable(); + return new Offset(o); + } +} + + +static public class PlayerStateVerify +{ + static public bool Verify(Google.FlatBuffers.Verifier verifier, uint tablePos) + { + return verifier.VerifyTableStart(tablePos) + && verifier.VerifyString(tablePos, 4 /*ProfileId*/, false) + && verifier.VerifyField(tablePos, 6 /*State*/, 1 /*StayInTarkov.FlatBuffers.EPlayerState*/, 1, false) + && verifier.VerifyField(tablePos, 8 /*Tilt*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 10 /*Step*/, 1 /*sbyte*/, 1, false) + && verifier.VerifyField(tablePos, 12 /*AnimatorStateIndex*/, 1 /*byte*/, 1, false) + && verifier.VerifyField(tablePos, 14 /*CharacterMovementSpeed*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 16 /*PoseLevel*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 18 /*IsProne*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 20 /*IsSprinting*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 22 /*HandsExhausted*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 24 /*OxygenExhausted*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 26 /*StaminaExhausted*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 28 /*Position*/, 12 /*StayInTarkov.FlatBuffers.Vec3*/, 4, false) + && verifier.VerifyField(tablePos, 30 /*Rotation*/, 8 /*StayInTarkov.FlatBuffers.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 32 /*HeadRotation*/, 12 /*StayInTarkov.FlatBuffers.Vec3*/, 4, false) + && verifier.VerifyField(tablePos, 34 /*MovementDirection*/, 8 /*StayInTarkov.FlatBuffers.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 36 /*InputDirection*/, 8 /*StayInTarkov.FlatBuffers.Vec2*/, 4, false) + && verifier.VerifyField(tablePos, 38 /*Blindfire*/, 1 /*sbyte*/, 1, false) + && verifier.VerifyField(tablePos, 40 /*LinearSpeed*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 42 /*LeftStance*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 44 /*IsAlive*/, 1 /*bool*/, 1, false) + && verifier.VerifyField(tablePos, 46 /*Energy*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 48 /*Hydration*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 50 /*Radiation*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 52 /*Poison*/, 4 /*float*/, 4, false) + && verifier.VerifyField(tablePos, 54 /*BodyPartsHealth*/, 64 /*StayInTarkov.FlatBuffers.BodyPartsHealth*/, 4, false) + && verifier.VerifyTableEnd(tablePos); + } +} + +} diff --git a/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/Vec2.cs b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/Vec2.cs new file mode 100644 index 000000000..53b6fa3f8 --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/Vec2.cs @@ -0,0 +1,31 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace StayInTarkov.FlatBuffers +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct Vec2 : IFlatbufferObject +{ + private Struct __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public void __init(int _i, ByteBuffer _bb) { __p = new Struct(_i, _bb); } + public Vec2 __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public float X { get { return __p.bb.GetFloat(__p.bb_pos + 0); } } + public float Y { get { return __p.bb.GetFloat(__p.bb_pos + 4); } } + + public static Offset CreateVec2(FlatBufferBuilder builder, float X, float Y) { + builder.Prep(4, 8); + builder.PutFloat(Y); + builder.PutFloat(X); + return new Offset(builder.Offset); + } +} + + +} diff --git a/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/Vec3.cs b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/Vec3.cs new file mode 100644 index 000000000..b7b6903e7 --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/StayInTarkov/FlatBuffers/Vec3.cs @@ -0,0 +1,33 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace StayInTarkov.FlatBuffers +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +public struct Vec3 : IFlatbufferObject +{ + private Struct __p; + public ByteBuffer ByteBuffer { get { return __p.bb; } } + public void __init(int _i, ByteBuffer _bb) { __p = new Struct(_i, _bb); } + public Vec3 __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; } + + public float X { get { return __p.bb.GetFloat(__p.bb_pos + 0); } } + public float Y { get { return __p.bb.GetFloat(__p.bb_pos + 4); } } + public float Z { get { return __p.bb.GetFloat(__p.bb_pos + 8); } } + + public static Offset CreateVec3(FlatBufferBuilder builder, float X, float Y, float Z) { + builder.Prep(4, 12); + builder.PutFloat(Z); + builder.PutFloat(Y); + builder.PutFloat(X); + return new Offset(builder.Offset); + } +} + + +} diff --git a/Source/Coop/NetworkPacket/FlatBuffers/player_state.fbs b/Source/Coop/NetworkPacket/FlatBuffers/player_state.fbs new file mode 100644 index 000000000..ab0dd6ada --- /dev/null +++ b/Source/Coop/NetworkPacket/FlatBuffers/player_state.fbs @@ -0,0 +1,108 @@ +namespace StayInTarkov.FlatBuffers; + +struct Vec2 { + x:float32; + y:float32; +} + +struct Vec3 { + x:float32; + y:float32; + z:float32; +} + +enum EPlayerState:uint8 { + None = 0, + Idle = 1, + ProneIdle = 2, + ProneMove = 3, + Run = 4, + Sprint = 5, + Jump = 6, + FallDown = 7, + Transition = 8, + BreachDoor = 9, + Loot = 10, + Pickup = 11, + Open = 12, + Close = 13, + Unlock = 14, + Sidestep = 15, + DoorInteraction = 16, + Approach = 17, + Prone2Stand = 18, + Transit2Prone = 19, + Plant = 20, + Stationary = 21, + Roll = 22, + JumpLanding = 23, + ClimbOver = 24, + ClimbUp = 25, + VaultingFallDown = 26, + VaultingLanding = 27, + BlindFire = 28 +} + +enum EBodyPart:uint8 { + Head = 0, + Chest = 1, + Stomach = 2, + LeftArm = 3, + RightArm = 4, + LeftLeg = 5, + RightLeg = 6, + Common = 7 +} + +struct BodyPartHealth { + current:float32; + maximum:float32; +} + +struct BodyPartsHealth { + parts:[BodyPartHealth:8]; +} + +// Unused for now, see HealthSyncPacket struct when needed +// struct HealthEffect { +// } + +table PlayerState { + profile_id:string; + state:EPlayerState; + tilt:float32; + step:int8; // [-1; 1]? + animator_state_index:uint8; // [0; 70] + character_movement_speed:float32; + pose_level:float32; + is_prone:bool; + is_sprinting:bool; + hands_exhausted:bool; + oxygen_exhausted:bool; + stamina_exhausted:bool; + position:Vec3; + rotation:Vec2; + head_rotation:Vec3; + movement_direction:Vec2; + input_direction:Vec2; + blindfire:int8; // [-1; 1] + linear_speed:float32; + left_stance:bool; + is_alive:bool; + energy: float32; + hydration: float32; + radiation: float32; + poison: float32; + body_parts_health:BodyPartsHealth; + // health_effects:[HealthEffect] +} + +union AnyPacket { + player_state:PlayerState +} + +table Packet { + packet: AnyPacket; +} + +root_type Packet; diff --git a/Source/Coop/NetworkPacket/Player/BasePlayerPacket.cs b/Source/Coop/NetworkPacket/Player/BasePlayerPacket.cs index 29fe1885b..2aea32aac 100644 --- a/Source/Coop/NetworkPacket/Player/BasePlayerPacket.cs +++ b/Source/Coop/NetworkPacket/Player/BasePlayerPacket.cs @@ -1,4 +1,7 @@ -using EFT.InventoryLogic; +using BepInEx.Logging; +using Comfort.Common; +using EFT; +using EFT.InventoryLogic; using Newtonsoft.Json; using StayInTarkov.Coop.Components.CoopGameComponents; using StayInTarkov.Coop.NetworkPacket.Player.Proceed; @@ -10,6 +13,13 @@ namespace StayInTarkov.Coop.NetworkPacket.Player { public class BasePlayerPacket : BasePacket { + public static ManualLogSource Logger { get; private set; } + + static BasePlayerPacket() + { + Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(BasePlayerPacket)); + } + [JsonProperty(PropertyName = "profileId")] public string ProfileId { get; set; } @@ -63,26 +73,36 @@ protected void WriteHeaderAndProfileId(BinaryWriter writer) writer.Write(ProfileId); } - //protected void WriteHeaderAndProfileId(NetworkWriter writer) - //{ - // WriteHeader(writer); - // writer.Write(ProfileId); - //} - /// /// Auto discover Client Player object and Process on them /// public override void Process() { - //StayInTarkovHelperConstants.Logger.LogDebug($"{GetType()}:{nameof(Process)}:{Method}"); - if (!SITGameComponent.TryGetCoopGameComponent(out var coopGameComponent)) + { + Logger.LogError("Unable to obtain Game Component"); + return; + } + + // If it is my Player, it wont be a Client. So ignore. + if (Singleton.Instantiated && Singleton.Instance.MainPlayer.ProfileId == ProfileId) return; + // Find the affected Player if (coopGameComponent.Players.ContainsKey(ProfileId) && coopGameComponent.Players[ProfileId] is CoopPlayerClient client) { Process(client); } + // This deals with other mods adding players to the world. TODO: Maybe remove the above call? + else if (Singleton.Instantiated && Singleton.Instance.allAlivePlayersByID.ContainsKey(ProfileId)) + { + if (Singleton.Instance.allAlivePlayersByID[ProfileId] is CoopPlayerClient client2) + Process(client2); + } + else + { + Logger.LogError($"Unable to find {ProfileId}"); + } } /// diff --git a/Source/Coop/NetworkPacket/Player/Health/RestoreBodyPartPacket.cs b/Source/Coop/NetworkPacket/Player/Health/RestoreBodyPartPacket.cs index 30ff64afa..bb3c0c1a1 100644 --- a/Source/Coop/NetworkPacket/Player/Health/RestoreBodyPartPacket.cs +++ b/Source/Coop/NetworkPacket/Player/Health/RestoreBodyPartPacket.cs @@ -1,9 +1,10 @@ -using System; +using EFT.HealthSystem; +using StayInTarkov.Coop.Components.CoopGameComponents; +using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using UnityEngine; +using static GClass2416; namespace StayInTarkov.Coop.NetworkPacket.Player.Health { @@ -12,10 +13,7 @@ public class RestoreBodyPartPacket : BasePlayerPacket public string BodyPart { get; set; } public float HealthPenalty { get; set; } - public RestoreBodyPartPacket() : base() - { - Method = "RestoreBodyPart"; - } + public RestoreBodyPartPacket() : base("", nameof(RestoreBodyPartPacket)) { } public override byte[] Serialize() { @@ -35,5 +33,64 @@ public override ISITPacket Deserialize(byte[] bytes) HealthPenalty = reader.ReadSingle(); return this; } + + public override void Process() + { + if (!SITGameComponent.TryGetCoopGameComponent(out var coopGameComponent)) + return; + + if (!coopGameComponent.Players.ContainsKey(ProfileId)) + return; + + var player = coopGameComponent.Players[ProfileId]; + + if (player.HealthController != null && player.HealthController.IsAlive) + { + StayInTarkovHelperConstants.Logger.LogInfo($"{nameof(PlayerInformationPacket)}:Replicated: Calling RestoreBodyPart"); + + var bodyPart = (EBodyPart)Enum.Parse(typeof(EBodyPart), BodyPart, true); + var bodyPartState = GetBodyPartDictionary(player)[bodyPart]; + + if (bodyPartState == null) + { + StayInTarkovHelperConstants.Logger.LogInfo($"{nameof(PlayerInformationPacket)}: Could not retrieve {player.ProfileId}'s Health State for Body Part {bodyPart}"); + return; + } + + if (bodyPartState.IsDestroyed) + { + bodyPartState.IsDestroyed = false; + var healthPenalty = HealthPenalty + (1f - HealthPenalty) * player.Skills.SurgeryReducePenalty; + StayInTarkovHelperConstants.Logger.LogInfo($"{nameof(PlayerInformationPacket)}:HealthPenalty::" + healthPenalty); + bodyPartState.Health = new HealthValue(1f, Mathf.Max(1f, Mathf.Ceil(bodyPartState.Health.Maximum * healthPenalty)), 0f); + + player.ExecuteSkill(new Action(player.Skills.SurgeryAction.Complete)); + player.UpdateSpeedLimitByHealth(); + } + } + } + + private Dictionary GetBodyPartDictionary(EFT.Player player) + { + try + { + var bodyPartDict + = ReflectionHelpers.GetFieldOrPropertyFromInstance> + (player.PlayerHealthController, "Dictionary_0", false); + if (bodyPartDict == null) + { + StayInTarkovHelperConstants.Logger.LogInfo($"{nameof(PlayerInformationPacket)}:Could not retrieve {player.ProfileId}'s Health State Dictionary"); + return null; + } + + return bodyPartDict; + } + catch (Exception ex) + { + StayInTarkovHelperConstants.Logger.LogError($"{nameof(PlayerInformationPacket)}: {ex}"); + } + + return null; + } } } diff --git a/Source/Coop/NetworkPacket/Player/Inventory/PlayerInventoryDropBackpackPacket.cs.cs b/Source/Coop/NetworkPacket/Player/Inventory/PlayerInventoryDropBackpackPacket.cs.cs new file mode 100644 index 000000000..b8d928a31 --- /dev/null +++ b/Source/Coop/NetworkPacket/Player/Inventory/PlayerInventoryDropBackpackPacket.cs.cs @@ -0,0 +1,31 @@ +using StayInTarkov.Coop.Players; + +namespace StayInTarkov.Coop.NetworkPacket.Player.Inventory +{ + /// + /// Goal: Backpack dropping in Polymorphing an inventory operation is too slow and only happens after the animation has already played + /// This causes an issue because the host still has to sync up by the time the client might have already picked up a new bag. + /// This Packet and the code referencing it fixes that. + /// + internal class PlayerInventoryDropBackpackPacket : ItemPlayerPacket + { + public PlayerInventoryDropBackpackPacket() : base("", "", "", nameof(PlayerInventoryDropBackpackPacket)) + { + } + + public override byte[] Serialize() + { + return base.Serialize(); + } + + public override ISITPacket Deserialize(byte[] bytes) + { + return base.Deserialize(bytes); + } + + protected override void Process(CoopPlayerClient client) + { + client.DropBackpack(); + } + } +} diff --git a/Source/Coop/NetworkPacket/Player/Inventory/PolymorphInventoryOperationPacket.cs b/Source/Coop/NetworkPacket/Player/Inventory/PolymorphInventoryOperationPacket.cs index d1e289d7b..c5b32be6e 100644 --- a/Source/Coop/NetworkPacket/Player/Inventory/PolymorphInventoryOperationPacket.cs +++ b/Source/Coop/NetworkPacket/Player/Inventory/PolymorphInventoryOperationPacket.cs @@ -141,7 +141,7 @@ private void HandleUnknownItem(CoopInventoryControllerClient pic, AbstractDescri if (descriptor is MoveOperationDescriptor moveOperationDescriptor) { var address = pic.ToItemAddress(moveOperationDescriptor.From); - ItemMovementHandler.Add(knownItem, address, pic); + InteractionsHandlerClass.Add(knownItem, address, pic); } } } diff --git a/Source/Coop/NetworkPacket/Player/PlayerGesturePacket.cs b/Source/Coop/NetworkPacket/Player/PlayerGesturePacket.cs new file mode 100644 index 000000000..51050e393 --- /dev/null +++ b/Source/Coop/NetworkPacket/Player/PlayerGesturePacket.cs @@ -0,0 +1,44 @@ +using StayInTarkov.Coop.Components.CoopGameComponents; +using StayInTarkov.Coop.Players; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StayInTarkov.Coop.NetworkPacket.Player +{ + public class PlayerGesturePacket : BasePlayerPacket + { + public PlayerGesturePacket() : base("", nameof(PlayerGesturePacket)) + { + + } + + public EGesture Gesture { get; set; } + + public override byte[] Serialize() + { + var ms = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(ms); + WriteHeaderAndProfileId(writer); + writer.Write((byte)Gesture); + return ms.ToArray(); + } + + public override ISITPacket Deserialize(byte[] bytes) + { + + using BinaryReader reader = new BinaryReader(new MemoryStream(bytes)); + ReadHeaderAndProfileId(reader); + Gesture = (EGesture)reader.ReadByte(); + return this; + } + + protected override void Process(CoopPlayerClient client) + { + client.vmethod_3(Gesture); + } + } +} diff --git a/Source/Coop/NetworkPacket/Player/PlayerInformationPacket.cs b/Source/Coop/NetworkPacket/Player/PlayerInformationPacket.cs index 7a86ba5f4..9368a8eec 100644 --- a/Source/Coop/NetworkPacket/Player/PlayerInformationPacket.cs +++ b/Source/Coop/NetworkPacket/Player/PlayerInformationPacket.cs @@ -97,7 +97,7 @@ public EFT.Profile Profile } - public EFT.Profile.ProfileHealth ProfileHealth { get; set; } + public ProfileHealth ProfileHealth { get; set; } //public ArenaObservedPlayerSpawnMessage ArenaObservedPlayerSpawnMessage; @@ -174,7 +174,7 @@ public override ISITPacket Deserialize(byte[] bytes) //var profileHealthString = reader.ReadString(); //StayInTarkovHelperConstants.Logger.LogInfo($"{profileHealthString}"); - //if (profileHealthString.TrySITParseJson(out EFT.Profile.ProfileHealth ph)) + //if (profileHealthString.TrySITParseJson(out EFT.ProfileHealth ph)) // ProfileHealth = ph; //else // StayInTarkovHelperConstants.Logger.LogError($"Unable to Process: {ProfileId}"); diff --git a/Source/Coop/NetworkPacket/Player/PlayerInteractWithDoorPacket.cs b/Source/Coop/NetworkPacket/Player/PlayerInteractWithDoorPacket.cs index 14e332b56..d2e6558b7 100644 --- a/Source/Coop/NetworkPacket/Player/PlayerInteractWithDoorPacket.cs +++ b/Source/Coop/NetworkPacket/Player/PlayerInteractWithDoorPacket.cs @@ -1,16 +1,11 @@ -using Comfort.Common; -using EFT; +using EFT; using EFT.Interactive; using EFT.InventoryLogic; using Newtonsoft.Json.Linq; using StayInTarkov.Coop.Components.CoopGameComponents; using StayInTarkov.Coop.Players; -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; -using System.Threading.Tasks; namespace StayInTarkov.Coop.NetworkPacket.Player { @@ -79,7 +74,7 @@ protected override void Process(CoopPlayerClient client) if (ProcessJson.ContainsKey("keyParentGrid")) { ItemAddress itemAddress = itemController.ToGridItemAddress(ProcessJson["keyParentGrid"].ToString().SITParseJson()); - discardResult = new DiscardResult(new RemoveResult(item, itemAddress, itemController, new ResizeResult(item, itemAddress, ItemMovementHandler.ResizeAction.Addition, null, null), null, false), null, null, null); + discardResult = new DiscardResult(new RemoveResult(item, itemAddress, itemController, new ResizeResult(item, itemAddress, InteractionsHandlerClass.ResizeAction.Addition, null, null), null, false), null, null, null); } keyInteractionResult = new KeyInteractionResult(keyComponent, discardResult, bool.Parse(ProcessJson["succeed"].ToString())); @@ -95,9 +90,7 @@ protected override void Process(CoopPlayerClient client) } } - - WorldInteractiveObject worldInteractiveObject = SITGameComponent.GetCoopGameComponent().ListOfInteractiveObjects.FirstOrDefault(x => x.Id == ProcessJson["WIOId"].ToString()); - if (worldInteractiveObject == null) + if (!SITGameComponent.GetCoopGameComponent().WorldnteractiveObjects.TryGetValue(ProcessJson["WIOId"].ToString(), out WorldInteractiveObject worldInteractiveObject)) { StayInTarkovHelperConstants.Logger.LogError($"Player_ExecuteDoorInteraction_Patch:Replicated:Could not find {nameof(worldInteractiveObject)}"); return; diff --git a/Source/Coop/NetworkPacket/Player/PlayerInteractWithObjectPacket.cs b/Source/Coop/NetworkPacket/Player/PlayerInteractWithObjectPacket.cs index 57aba09b1..07ae22935 100644 --- a/Source/Coop/NetworkPacket/Player/PlayerInteractWithObjectPacket.cs +++ b/Source/Coop/NetworkPacket/Player/PlayerInteractWithObjectPacket.cs @@ -5,7 +5,6 @@ using StayInTarkov.Coop.Components.CoopGameComponents; using StayInTarkov.Coop.Players; using System.IO; -using System.Linq; using System.Text; namespace StayInTarkov.Coop.NetworkPacket.Player @@ -56,9 +55,7 @@ protected override void Process(CoopPlayerClient client) return; } - WorldInteractiveObject worldInteractiveObject = SITGameComponent.GetCoopGameComponent().ListOfInteractiveObjects.FirstOrDefault(x => x.Id == ProcessJson["WIOId"].ToString()); - - if (worldInteractiveObject == null) + if (!SITGameComponent.GetCoopGameComponent().WorldnteractiveObjects.TryGetValue(ProcessJson["WIOId"].ToString(), out WorldInteractiveObject worldInteractiveObject)) { StayInTarkovHelperConstants.Logger.LogError($"Player_ExecuteDoorInteraction_Patch:Replicated:Could not find {nameof(worldInteractiveObject)}"); return; @@ -82,7 +79,7 @@ protected override void Process(CoopPlayerClient client) if (ProcessJson.ContainsKey("keyParentGrid")) { ItemAddress itemAddress = itemController.ToGridItemAddress(ProcessJson["keyParentGrid"].ToString().SITParseJson()); - discardResult = new DiscardResult(new RemoveResult(item, itemAddress, itemController, new ResizeResult(item, itemAddress, ItemMovementHandler.ResizeAction.Addition, null, null), null, false), null, null, null); + discardResult = new DiscardResult(new RemoveResult(item, itemAddress, itemController, new ResizeResult(item, itemAddress, InteractionsHandlerClass.ResizeAction.Addition, null, null), null, false), null, null, null); } keyInteractionResult = new KeyInteractionResult(keyComponent, discardResult, bool.Parse(ProcessJson["succeed"].ToString())); diff --git a/Source/Coop/NetworkPacket/Player/PlayerStatePacket.cs b/Source/Coop/NetworkPacket/Player/PlayerStatePacket.cs deleted file mode 100644 index d4224f1e1..000000000 --- a/Source/Coop/NetworkPacket/Player/PlayerStatePacket.cs +++ /dev/null @@ -1,183 +0,0 @@ -using LiteNetLib.Utils; -using StayInTarkov.Coop.Components.CoopGameComponents; -using StayInTarkov.Coop.NetworkPacket.Player.Health; -using StayInTarkov.Coop.Players; -using StayInTarkov.ThirdParty; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using UnityEngine; -using static StayInTarkov.Networking.SITSerialization; - -namespace StayInTarkov.Coop.NetworkPacket.Player -{ - public sealed class PlayerStatePacket : BasePlayerPacket, INetSerializable - { - public EPlayerState State { get; set; } - public float Tilt { get; set; } - public int Step { get; set; } - public int AnimatorStateIndex { get; set; } - public float CharacterMovementSpeed { get; set; } - public bool IsProne { get; set; } - public float PoseLevel { get; set; } - public bool IsSprinting { get; set; } - public bool HandsExhausted { get; set; } - public bool OxygenExhausted { get; set; } - public bool StaminaExhausted { get; set; } - public Vector3 Position { get; set; } - public Vector2 Rotation { get; set; } - public Vector3 HeadRotation { get; set; } - public Vector2 MovementDirection { get; set; } - public Physical.PhysicalStamina Stamina { get; set; } - public Vector2 InputDirection { get; set; } - public int Blindfire { get; set; } - public float LinearSpeed { get; set; } - public bool LeftStance { get; set; } - - public PlayerHealthPacket PlayerHealth { get; set; } - - public PlayerStatePacket() : base("", nameof(PlayerStatePacket)) - { - } - - public PlayerStatePacket(string profileId, Vector3 position, Vector2 rotation, Vector3 headRotation, Vector2 movementDirection, - EPlayerState state, float tilt, int step, int animatorStateIndex, float characterMovementSpeed, - bool isProne, float poseLevel, bool isSprinting, Vector2 inputDirection, bool leftStance - , PlayerHealthPacket playerHealth, Physical.PhysicalStamina stamina, int blindFire, float linearSpeed) - : base(new string(profileId.ToCharArray()), nameof(PlayerStatePacket)) - { - Position = position; - Rotation = rotation; - HeadRotation = headRotation; - MovementDirection = movementDirection; - State = state; - Tilt = tilt; - Step = step; - AnimatorStateIndex = animatorStateIndex; - CharacterMovementSpeed = characterMovementSpeed; - IsProne = isProne; - PoseLevel = poseLevel; - IsSprinting = isSprinting; - InputDirection = inputDirection; - PlayerHealth = playerHealth; - Stamina = stamina; - Blindfire = blindFire; - LinearSpeed = linearSpeed; - LeftStance = leftStance; - } - - public override byte[] Serialize() - { - var ms = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(ms); - WriteHeaderAndProfileId(writer); - Vector3Utils.Serialize(writer, Position); - Vector2Utils.Serialize(writer, Rotation); - Vector2Utils.Serialize(writer, HeadRotation); - Vector2Utils.Serialize(writer, MovementDirection); - writer.Write(State.ToString()); - writer.Write(BSGNetworkConversionHelpers.ScaleFloatToByte(Tilt, -5f, 5f)); - writer.Write(Step); - writer.Write(AnimatorStateIndex); - writer.Write(BSGNetworkConversionHelpers.ScaleFloatToByte(CharacterMovementSpeed, 0f, 1f)); - writer.Write(IsProne); - writer.Write(PoseLevel); - writer.Write(IsSprinting); - PhysicalUtils.Serialize(writer, Stamina); - Vector2Utils.Serialize(writer, InputDirection); - writer.Write(LeftStance); - writer.Write(Blindfire); - writer.Write(LinearSpeed); - writer.Write(TimeSerializedBetter); - - // Has PlayerHealth packet - writer.Write(PlayerHealth != null); - if (PlayerHealth != null) - writer.WriteLengthPrefixedBytes(PlayerHealth.Serialize()); - - //return Zlib.Compress(ms.ToArray()); - return ms.ToArray(); - - } - - public override ISITPacket Deserialize(byte[] bytes) - { - if (bytes == null) - throw new ArgumentNullException(nameof(bytes)); - - //bytes = Zlib.DecompressToBytes(bytes); - - using BinaryReader reader = new BinaryReader(new MemoryStream(bytes)); - ReadHeaderAndProfileId(reader); - Position = Vector3Utils.Deserialize(reader); - Rotation = Vector2Utils.Deserialize(reader); - HeadRotation = Vector2Utils.Deserialize(reader); - MovementDirection = Vector2Utils.Deserialize(reader); - State = (EPlayerState)Enum.Parse(typeof(EPlayerState), reader.ReadString()); - Tilt = BSGNetworkConversionHelpers.ScaleByteToFloat(reader.ReadByte(), -5f, 5f); - Step = reader.ReadInt32(); - AnimatorStateIndex = reader.ReadInt32(); - CharacterMovementSpeed = BSGNetworkConversionHelpers.ScaleByteToFloat(reader.ReadByte(), 0f, 1f); - IsProne = reader.ReadBoolean(); - PoseLevel = reader.ReadSingle(); - IsSprinting = reader.ReadBoolean(); - Stamina = PhysicalUtils.Deserialize(reader); - InputDirection = Vector2Utils.Deserialize(reader); - LeftStance = reader.ReadBoolean(); - Blindfire = reader.ReadInt32(); - LinearSpeed = reader.ReadSingle(); - TimeSerializedBetter = reader.ReadString(); - - // If has a PlayerHealth packet - if (reader.ReadBoolean()) - { - PlayerHealth = new PlayerHealthPacket(ProfileId); - PlayerHealth = (PlayerHealthPacket)PlayerHealth.Deserialize(reader.ReadLengthPrefixedBytes()); - } - - //StayInTarkovHelperConstants.Logger.LogInfo($"{nameof(PlayerStatePacket)}:{nameof(Deserialize)}:{this.SITToJson()}"); - return this; - } - - void INetSerializable.Serialize(NetDataWriter writer) - { - var serializedSIT = Serialize(); - writer.Put(serializedSIT.Length); - writer.Put(serializedSIT); - } - - void INetSerializable.Deserialize(NetDataReader reader) - { - var length = reader.GetInt(); - byte[] bytes = new byte[length]; - reader.GetBytes(bytes, length); - Deserialize(bytes); - } - - public override bool Equals(object obj) - { - if (obj is PlayerStatePacket other) - { - return other.ProfileId == ProfileId - && other.Position.IsEqual(Position, 1) - && other.Rotation.Equals(Rotation) - ; - } - return base.Equals(obj); - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - - protected override void Process(CoopPlayerClient client) - { - client.ReceivePlayerStatePacket(this); - } - } -} diff --git a/Source/Coop/NetworkPacket/Player/PlayerStatesPacket.cs b/Source/Coop/NetworkPacket/Player/PlayerStatesPacket.cs deleted file mode 100644 index 38e6ba5a6..000000000 --- a/Source/Coop/NetworkPacket/Player/PlayerStatesPacket.cs +++ /dev/null @@ -1,74 +0,0 @@ -using StayInTarkov.Coop.Components.CoopGameComponents; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Sockets; -using System.Text; -using System.Threading.Tasks; - -namespace StayInTarkov.Coop.NetworkPacket.Player -{ - public sealed class PlayerStatesPacket : BasePacket - { - public PlayerStatePacket[] PlayerStates { get; set; } - public PlayerStatesPacket() : base(nameof(PlayerStatesPacket)) - { - - } - - public PlayerStatesPacket(in PlayerStatePacket[] statePackets) : base(nameof(PlayerStatesPacket)) - { - PlayerStates = statePackets; - } - - public override byte[] Serialize() - { - using var ms = new MemoryStream(); - using BinaryWriter binaryWriter = new BinaryWriter(ms); - WriteHeader(binaryWriter); - binaryWriter.Write(PlayerStates.Length); - foreach (var state in PlayerStates) - binaryWriter.WriteLengthPrefixedBytes(state.Serialize()); - binaryWriter.Write(TimeSerializedBetter); - return ms.ToArray(); - } - - public override ISITPacket Deserialize(byte[] bytes) - { - using BinaryReader reader = new BinaryReader(new MemoryStream(bytes)); - ReadHeader(reader); - var length = reader.ReadInt32(); - PlayerStates = new PlayerStatePacket[length]; - for (var i = 0; i < length; i++) - PlayerStates[i] = new PlayerStatePacket().Deserialize(reader.ReadLengthPrefixedBytes()) as PlayerStatePacket; - TimeSerializedBetter = reader.ReadString(); - return this; - } - - public override void Process() - { - //StayInTarkovHelperConstants.Logger.LogInfo($"{nameof(PlayerStatesPacket)}:{nameof(Process)}:{this.SITToJson()}"); - - for (var i = 0; i < PlayerStates.Length; i++) - PlayerStates[i].Process(); - } - - protected override void Dispose(bool disposing) - { - if (!disposedValue) - { - //StayInTarkovHelperConstants.Logger.LogInfo($"{nameof(PlayerStatesPacket)}:{nameof(Dispose)}"); - for (var i = 0; i < PlayerStates.Length; i++) - { - PlayerStates[i].Dispose(); - PlayerStates[i] = null; - } - PlayerStates = null; - } - base.Dispose(disposing); - } - - - } -} diff --git a/Source/Coop/NetworkPacket/Player/Proceed/PlayerProceedGrenadePacket.cs b/Source/Coop/NetworkPacket/Player/Proceed/PlayerProceedGrenadePacket.cs index 8665a6ec0..74e383e5a 100644 --- a/Source/Coop/NetworkPacket/Player/Proceed/PlayerProceedGrenadePacket.cs +++ b/Source/Coop/NetworkPacket/Player/Proceed/PlayerProceedGrenadePacket.cs @@ -53,7 +53,7 @@ protected override void Process(CoopPlayerClient client) { if (ItemFinder.TryFindItem(ItemId, out var item) && item is GrenadeClass grenade) { - client.Proceed(grenade, (Comfort.Common.Result x) => { }, Scheduled); + client.Proceed(grenade, (Comfort.Common.Result x) => { }, Scheduled); } } } diff --git a/Source/Coop/NetworkPacket/Player/Weapons/InitiateShotPacket.cs b/Source/Coop/NetworkPacket/Player/Weapons/InitiateShotPacket.cs index 549aa29aa..3eaaede61 100644 --- a/Source/Coop/NetworkPacket/Player/Weapons/InitiateShotPacket.cs +++ b/Source/Coop/NetworkPacket/Player/Weapons/InitiateShotPacket.cs @@ -4,14 +4,12 @@ */ using BepInEx.Logging; -using Comfort.Common; using EFT; using EFT.InventoryLogic; using StayInTarkov.Coop.Controllers.HandControllers; using StayInTarkov.Coop.Players; using System.Collections; using System.IO; -using System.Net.Sockets; using UnityEngine; using static StayInTarkov.Networking.SITSerialization; @@ -55,7 +53,7 @@ public InitiateShotPacket(string profileId) : base(new string(profileId.ToCharAr public override byte[] Serialize() { var ms = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(ms); + using BinaryWriter writer = new(ms); WriteHeaderAndProfileId(writer); writer.Write(IsPrimaryActive); writer.Write(AmmoAfterShot); @@ -71,7 +69,7 @@ public override byte[] Serialize() public override ISITPacket Deserialize(byte[] bytes) { - using BinaryReader reader = new BinaryReader(new MemoryStream(bytes)); + using BinaryReader reader = new(new MemoryStream(bytes)); ReadHeaderAndProfileId(reader); IsPrimaryActive = reader.ReadBoolean(); AmmoAfterShot = reader.ReadInt32(); @@ -88,7 +86,7 @@ public override ISITPacket Deserialize(byte[] bytes) protected override void Process(CoopPlayerClient client) { - if(client.HandsController is SITFirearmControllerClient firearmControllerClient) + if (client.HandsController is SITFirearmControllerClient firearmControllerClient) { GetBulletToFire(client, firearmControllerClient.Weapon, out var ammoToFire); if (ammoToFire == null) @@ -138,9 +136,9 @@ protected override void Process(CoopPlayerClient client) } break; case EShotType.RegularShot: + firearmControllerClient.InitiateShot(firearmControllerClient.Weapon, ammoToFire, ShotPosition, ShotDirection, FireportPosition, ChamberIndex, Overheat); firearmControllerClient.PlaySounds(firearmControllerClient.WeaponSoundPlayer, ammoToFire, ShotPosition, ShotDirection, false); - //firearmControllerClient.FirearmsAnimator.SetFire(fire: true); if (firearmControllerClient.Weapon.IsBoltCatch && firearmControllerClient.Weapon.ChamberAmmoCount == 0 && firearmControllerClient.Weapon.GetCurrentMagazineCount() == 0 && !firearmControllerClient.Weapon.ManualBoltCatch) { @@ -159,6 +157,7 @@ protected override void Process(CoopPlayerClient client) if (firearmControllerClient.Weapon.GetCurrentMagazine() is CylinderMagazineClass cylindermag) { + cylindermag.Camoras[cylindermag.CurrentCamoraIndex].RemoveItem(); cylindermag.IncrementCamoraIndex(); firearmControllerClient.FirearmsAnimator.SetCamoraIndex(cylindermag.CurrentCamoraIndex); } @@ -169,10 +168,8 @@ protected override void Process(CoopPlayerClient client) firearmControllerClient.LightAndSoundShot(ShotPosition, ShotDirection, ammoToFire.AmmoTemplate); } - //if (firearmControllerClient.Weapon.HasChambers && firearmControllerClient.Weapon.Chambers[0].ContainedItem != null) - // firearmControllerClient.Weapon.Chambers[0].RemoveItem().OrElse(elseValue: false); - //ammoToFire.IsUsed = true; + break; default: @@ -193,15 +190,22 @@ private IEnumerator DisableBoltActionAnim(SITFirearmControllerClient client) private void GetBulletToFire(CoopPlayerClient client, Weapon weapon_0, out BulletClass ammoToFire) { + var pic = ItemFinder.GetPlayerInventoryController(client); + if (weapon_0.GetCurrentMagazine() is CylinderMagazineClass cylindermag) { - ammoToFire = cylindermag.GetFirstAmmo(singleFireMode: false); - // Is Used? - ammoToFire.IsUsed = true; + //ammoToFire = cylindermag.GetFirstAmmo(singleFireMode: false); + + ammoToFire = cylindermag.Cartridges.Count > 0 + ? (BulletClass)cylindermag.Cartridges.PopToNowhere(pic).Value.Item + : cylindermag.Camoras.Length > 0 ? (BulletClass)cylindermag.Camoras[cylindermag.CurrentCamoraIndex].ContainedItem + : null; + + Logger.LogDebug($"Used CylinderMagazineClass {ammoToFire}"); + return; } - var pic = ItemFinder.GetPlayerInventoryController(client); // Find the Ammo in the Chamber Slot[] chambers = weapon_0.Chambers; @@ -209,7 +213,10 @@ private void GetBulletToFire(CoopPlayerClient client, Weapon weapon_0, out Bulle // ammoToFire if (ammoToFire != null) { +#if DEBUG Logger.LogDebug($"Used {ammoToFire} in Chamber"); +#endif + weapon_0.Chambers[0].RemoveItem().OrElse(elseValue: false); return; } @@ -228,23 +235,24 @@ private void GetBulletToFire(CoopPlayerClient client, Weapon weapon_0, out Bulle //} //else //{ - ammoToFire = (BulletClass)currentMagazine.Cartridges.PopToNowhere(pic).Value.Item; - - // Logger.LogDebug($"Popped {ammoToFire} to nowhere"); + ammoToFire = (BulletClass)currentMagazine.Cartridges.PopToNowhere(pic).Value.Item; +#if DEBUG + Logger.LogDebug($"Popped {ammoToFire} to nowhere"); +#endif //} - + } // Is Used? - ammoToFire.IsUsed = true; - + //ammoToFire.IsUsed = true; + if (ammoToFire == null) { Logger.LogError($"Unable to find Ammo to Fire for {client.ProfileId} weapon {weapon_0}"); } } - + } } diff --git a/Source/Coop/NetworkPacket/Player/Weapons/ReloadWithAmmoPacket.cs b/Source/Coop/NetworkPacket/Player/Weapons/ReloadWithAmmoPacket.cs index 0dac8a783..c41320331 100644 --- a/Source/Coop/NetworkPacket/Player/Weapons/ReloadWithAmmoPacket.cs +++ b/Source/Coop/NetworkPacket/Player/Weapons/ReloadWithAmmoPacket.cs @@ -48,9 +48,15 @@ public override ISITPacket Deserialize(byte[] bytes) return this; } + public override void Process() + { + StayInTarkovHelperConstants.Logger.LogDebug($"{GetType()}:{nameof(Process)}()"); + base.Process(); + } + protected override void Process(CoopPlayerClient client) { - StayInTarkovHelperConstants.Logger.LogDebug($"{GetType()}:{nameof(Process)}"); + StayInTarkovHelperConstants.Logger.LogDebug($"{GetType()}:{nameof(Process)}(client)"); List ammoList = new(); foreach (string ammoId in AmmoIds) diff --git a/Source/Coop/NetworkPacket/README.md b/Source/Coop/NetworkPacket/README.md index 869f64384..30e0b5e1f 100644 --- a/Source/Coop/NetworkPacket/README.md +++ b/Source/Coop/NetworkPacket/README.md @@ -1,11 +1,23 @@ # Network Packets -# Explanation +## Explanation - All network packets for Stay In Tarkov should be kept in the Network Packet folder - Network packets must inherit ISITPacket -- 99% of your network packets will inherit BasePlayerPacket or BasePacket which have good helpers for Serialize and Deserialize of packets +- Most of your network packets will inherit BasePlayerPacket or BasePacket which have good helpers for Serialize and Deserialize of packets +- There is a performance-first option available for performance-critical packets like player states sent every tick, or player spawn that can be quite voluminous and happen in bursts. It uses FlatBuffers, which requires building a schema beforehand. -# BasePlayerPacket +## BasePlayerPacket -Inherit BasePlayerPacket for any packet that you wish to be replicated by a Coop Player. Stay in Tarkov's networking has a system which can process this packet and the Process(CoopPlayerClient) is overridable to use. \ No newline at end of file +Inherit BasePlayerPacket for any packet that you wish to be replicated by a Coop Player. Stay in Tarkov's networking has a system which can process this packet and the Process(CoopPlayerClient) is overridable to use. + +## FlatBuffers + +For performance-critical packets where we're fine trading dev pace for runtime performance, we use [FlatBuffers](https://flatbuffers.dev/flatbuffers_guide_tutorial.html) to have zero (de)serialization overhead and still a compact on-the-wire format. +This requires compiling `.fbs` files to generate C# classes. This can be done with the `flatc` compiler, which needs to be built separately. + +``` +# From Visual Studio, Tools -> Command Line -> Developer PowerShell +vcpkg install +.\vcpkg_installed\x64-windows\tools\flatbuffers\flatc --csharp +``` \ No newline at end of file diff --git a/Source/Coop/NetworkPacket/Raid/ExtractedPlayerPacket.cs b/Source/Coop/NetworkPacket/Raid/ExtractedPlayerPacket.cs new file mode 100644 index 000000000..da50d001c --- /dev/null +++ b/Source/Coop/NetworkPacket/Raid/ExtractedPlayerPacket.cs @@ -0,0 +1,33 @@ +using Comfort.Common; +using EFT; +using StayInTarkov.Coop.NetworkPacket.Player; +using StayInTarkov.Coop.Players; +using StayInTarkov.Coop.SITGameModes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StayInTarkov.Coop.NetworkPacket.Raid +{ + public sealed class ExtractedPlayerPacket : BasePlayerPacket + { + public ExtractedPlayerPacket() : base("", nameof(ExtractedPlayerPacket)) + { + } + + public ExtractedPlayerPacket(string profileId) : base(profileId, nameof(ExtractedPlayerPacket)) + { + this.ProfileId = profileId; + } + + protected override void Process(CoopPlayerClient client) + { + StayInTarkovHelperConstants.Logger.LogDebug($"{nameof(ExtractedPlayerPacket)}:Process({client.ProfileId})"); + var gameInstance = Singleton.Instance; + if (!gameInstance.ExtractedPlayers.Contains(client.ProfileId)) + gameInstance.ExtractedPlayers.Add(client.ProfileId); + } + } +} diff --git a/Source/Coop/NetworkPacket/Raid/LootableContainerInteractionPacket.cs b/Source/Coop/NetworkPacket/Raid/LootableContainerInteractionPacket.cs new file mode 100644 index 000000000..273baf1f2 --- /dev/null +++ b/Source/Coop/NetworkPacket/Raid/LootableContainerInteractionPacket.cs @@ -0,0 +1,81 @@ +using EFT; +using EFT.Interactive; +using StayInTarkov.Coop.Components.CoopGameComponents; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static GClass1885; + +namespace StayInTarkov.Coop.NetworkPacket.Raid +{ + public sealed class LootableContainerInteractionPacket : BasePacket + { + public LootableContainerInteractionPacket() : base(nameof(LootableContainerInteractionPacket)) + { + + } + + public string LootableContainerId { get; set; } + public EInteractionType InteractionType { get; set; } + + public override byte[] Serialize() + { + var ms = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(ms); + WriteHeader(writer); + writer.Write(LootableContainerId); + writer.Write((byte)InteractionType); + return ms.ToArray(); + } + + public override ISITPacket Deserialize(byte[] bytes) + { + using BinaryReader reader = new BinaryReader(new MemoryStream(bytes)); + ReadHeader(reader); + LootableContainerId = reader.ReadString(); + InteractionType = (EInteractionType)reader.ReadByte(); + return this; + } + + public override void Process() + { + SITGameComponent coopGameComponent = SITGameComponent.GetCoopGameComponent(); + if (!coopGameComponent.WorldnteractiveObjects.TryGetValue(LootableContainerId, out WorldInteractiveObject worldInteractiveObject)) + { + StayInTarkovHelperConstants.Logger.LogError($"LootableContainerInteractionPacket:Process:Could not find {nameof(worldInteractiveObject)}"); + return; + } + LootableContainer lootableContainer = worldInteractiveObject as LootableContainer; + + string methodName = string.Empty; + switch (InteractionType) + { + case EInteractionType.Open: + methodName = "Open"; + break; + case EInteractionType.Close: + methodName = "Close"; + break; + case EInteractionType.Unlock: + methodName = "Unlock"; + break; + case EInteractionType.Breach: + break; + case EInteractionType.Lock: + methodName = "Lock"; + break; + } + + void Interact() => ReflectionHelpers.InvokeMethodForObject(lootableContainer, methodName); + + if (InteractionType == EInteractionType.Unlock) + Interact(); + else + lootableContainer.StartBehaviourTimer(EFTHardSettings.Instance.DelayToOpenContainer, Interact); + + } + } +} diff --git a/Source/Coop/NetworkPacket/Raid/RaidTimerPacket.cs b/Source/Coop/NetworkPacket/Raid/RaidTimerPacket.cs index 35c43a5c0..5c6c9bdd9 100644 --- a/Source/Coop/NetworkPacket/Raid/RaidTimerPacket.cs +++ b/Source/Coop/NetworkPacket/Raid/RaidTimerPacket.cs @@ -1,20 +1,27 @@ -using EFT.UI.BattleTimer; +using BepInEx.Logging; +using Comfort.Common; +using EFT; +using EFT.UI.BattleTimer; +using HarmonyLib.Tools; using StayInTarkov.Coop.Components.CoopGameComponents; using StayInTarkov.Coop.Matchmaker; using StayInTarkov.Coop.SITGameModes; using System; -using System.Collections.Generic; +using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace StayInTarkov.Coop.NetworkPacket.Raid { internal sealed class RaidTimerPacket : BasePacket { + static RaidTimerPacket() + { + Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(RaidTimerPacket)); + } + public long SessionTime { get; set; } + public static ManualLogSource Logger { get; } public RaidTimerPacket() : base(nameof(RaidTimerPacket)) { @@ -23,7 +30,7 @@ public RaidTimerPacket() : base(nameof(RaidTimerPacket)) public override byte[] Serialize() { var ms = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(ms); + using BinaryWriter writer = new(ms); WriteHeader(writer); writer.Write(SessionTime); return ms.ToArray(); @@ -31,7 +38,7 @@ public override byte[] Serialize() public override ISITPacket Deserialize(byte[] bytes) { - using BinaryReader reader = new BinaryReader(new MemoryStream(bytes)); + using BinaryReader reader = new(new MemoryStream(bytes)); ReadHeader(reader); SessionTime = reader.ReadInt64(); return this; @@ -48,9 +55,24 @@ public override void Process() var sessionTime = new TimeSpan(SessionTime); - if (coopGameComponent.LocalGameInstance is CoopSITGame coopGame) + if (!Singleton.Instantiated) + { + Logger.LogError($"{nameof(Process)} failed {nameof(ISITGame)} was not instantiated!"); + return; + } + + if (!Singleton.Instantiated) + { + Logger.LogError($"{nameof(Process)} failed {nameof(AbstractGame)} was not instantiated!"); + return; + } + + var sitGame = Singleton.Instance; + var abstractGame = Singleton.Instance; + + //if (coopGameComponent.LocalGameInstance is CoopSITGame coopGame) { - var gameTimer = coopGame.GameTimer; + var gameTimer = sitGame.GameTimer; if (gameTimer.StartDateTime.HasValue && gameTimer.SessionTime.HasValue) { if (gameTimer.PastTime.TotalSeconds < 3) @@ -64,7 +86,7 @@ public override void Process() StayInTarkovHelperConstants.Logger.LogInfo($"RaidTimer: New SessionTime {timeRemain.TraderFormat()}"); gameTimer.ChangeSessionTime(timeRemain); - MainTimerPanel mainTimerPanel = ReflectionHelpers.GetFieldOrPropertyFromInstance(coopGame.GameUi.TimerPanel, "_mainTimerPanel", false); + MainTimerPanel mainTimerPanel = ReflectionHelpers.GetFieldOrPropertyFromInstance(abstractGame.GameUi.TimerPanel, "_mainTimerPanel", false); if (mainTimerPanel != null) { FieldInfo extractionDateTimeField = ReflectionHelpers.GetFieldFromType(typeof(TimerPanel), "dateTime_0"); diff --git a/Source/Coop/NetworkPacket/Raid/TimeAndWeatherPacket.cs b/Source/Coop/NetworkPacket/Raid/TimeAndWeatherPacket.cs new file mode 100644 index 000000000..f9cb3d962 --- /dev/null +++ b/Source/Coop/NetworkPacket/Raid/TimeAndWeatherPacket.cs @@ -0,0 +1,165 @@ +using StayInTarkov.Coop.Components.CoopGameComponents; +using StayInTarkov.Coop.Matchmaker; +using StayInTarkov.Coop.SITGameModes; +using StayInTarkov.Networking; +using System; +using System.IO; +using System.Linq; +using UnityEngine; + +namespace StayInTarkov.Coop.NetworkPacket.Raid +{ + public sealed class TimeAndWeatherPacket : BasePacket + { + public TimeAndWeatherPacket() : base(nameof(TimeAndWeatherPacket)) + { + } + + public float GameDateTime { get; set; } + public float CloudDensity { get; set; } + public float Fog { get; set; } + public float LightningThunderProbability { get; set; } + public float Rain { get; set; } + public float Temperature { get; set; } + public Vector2 Wind { get; set; } + public Vector2 TopWind { get; set; } + + public override byte[] Serialize() + { + var ms = new MemoryStream(); + using BinaryWriter writer = new(ms); + WriteHeader(writer); + //writer.Write(GameDateTime); + writer.Write(CloudDensity); + writer.Write(Fog); + writer.Write(LightningThunderProbability); + writer.Write(Rain); + writer.Write(Temperature); + SITSerialization.Vector2Utils.Serialize(writer, Wind); + SITSerialization.Vector2Utils.Serialize(writer, TopWind); + + return ms.ToArray(); + } + + public override ISITPacket Deserialize(byte[] bytes) + { + using BinaryReader reader = new(new MemoryStream(bytes)); + ReadHeader(reader); + //GameDateTime = reader.ReadSingle(); + CloudDensity = reader.ReadSingle(); + Fog = reader.ReadSingle(); + LightningThunderProbability = reader.ReadSingle(); + Rain = reader.ReadSingle(); + Temperature = reader.ReadSingle(); + Wind = SITSerialization.Vector2Utils.Deserialize(reader); + TopWind = SITSerialization.Vector2Utils.Deserialize(reader); + return this; + } + + public override void Process() + { + ReplicateTimeAndWeather(); + } + + void ReplicateTimeAndWeather() + { + SITGameComponent coopGameComponent = SITGameComponent.GetCoopGameComponent(); + if (coopGameComponent == null) + return; + + if (!SITMatchmaking.IsClient) + return; + + //#if DEBUG + // StayInTarkov.StayInTarkovHelperConstants.Logger.LogDebug($"{nameof(ReplicateTimeAndWeather)}"); + + //#endif + + var gameDateTime = new DateTime(long.Parse(GameDateTime.ToString())); + if (coopGameComponent.LocalGameInstance is CoopSITGame coopGame && coopGame.GameDateTime != null) + coopGame.GameDateTime.Reset(gameDateTime); + + var weatherController = EFT.Weather.WeatherController.Instance; + if (weatherController != null) + { + var weatherDebug = weatherController.WeatherDebug; + if (weatherDebug != null) + { + weatherDebug.Enabled = true; + + weatherDebug.CloudDensity = float.Parse(this.CloudDensity.ToString()); + weatherDebug.Fog = float.Parse(this.Fog.ToString()); + weatherDebug.LightningThunderProbability = float.Parse(this.LightningThunderProbability.ToString()); + weatherDebug.Rain = float.Parse(this.Rain.ToString()); + weatherDebug.Temperature = float.Parse(this.Temperature.ToString()); + weatherDebug.TopWindDirection = this.TopWind; + + Vector2 windDirection = this.Wind; + + // working dog sh*t, if you are the programmer, DON'T EVER DO THIS! - dounai2333 + static bool BothPositive(float f1, float f2) => f1 > 0 && f2 > 0; + static bool BothNegative(float f1, float f2) => f1 < 0 && f2 < 0; + static bool VectorIsSameQuadrant(Vector2 v1, Vector2 v2, out int flag) + { + flag = 0; + if (v1.x != 0 && v1.y != 0 && v2.x != 0 && v2.y != 0) + { + if ((BothPositive(v1.x, v2.x) && BothPositive(v1.y, v2.y)) + || (BothNegative(v1.x, v2.x) && BothNegative(v1.y, v2.y)) + || (BothPositive(v1.x, v2.x) && BothNegative(v1.y, v2.y)) + || (BothNegative(v1.x, v2.x) && BothPositive(v1.y, v2.y))) + { + flag = 1; + return true; + } + } + else + { + if (v1.x != 0 && v2.x != 0) + { + if (BothPositive(v1.x, v2.x) || BothNegative(v1.x, v2.x)) + { + flag = 1; + return true; + } + } + else if (v1.y != 0 && v2.y != 0) + { + if (BothPositive(v1.y, v2.y) || BothNegative(v1.y, v2.y)) + { + flag = 2; + return true; + } + } + } + return false; + } + + for (int i = 1; i < WeatherClass.WindDirections.Count(); i++) + { + Vector2 direction = WeatherClass.WindDirections[i]; + if (VectorIsSameQuadrant(windDirection, direction, out int flag)) + { + weatherDebug.WindDirection = (EFT.Weather.WeatherDebug.Direction)i; + weatherDebug.WindMagnitude = flag switch + { + 1 => windDirection.x / direction.x, + 2 => windDirection.y / direction.y, + _ => weatherDebug.WindMagnitude + }; + break; + } + } + } + else + { + StayInTarkov.StayInTarkovHelperConstants.Logger.LogError("TimeAndWeather: WeatherDebug is null!"); + } + } + else + { + StayInTarkov.StayInTarkovHelperConstants.Logger.LogError("TimeAndWeather: WeatherController is null!"); + } + } + } +} diff --git a/Source/Coop/NetworkPacket/World/UpdateExfiltrationPointPacket.cs b/Source/Coop/NetworkPacket/World/UpdateExfiltrationPointPacket.cs index 630620e81..48f03e647 100644 --- a/Source/Coop/NetworkPacket/World/UpdateExfiltrationPointPacket.cs +++ b/Source/Coop/NetworkPacket/World/UpdateExfiltrationPointPacket.cs @@ -13,12 +13,12 @@ public sealed class UpdateExfiltrationPointPacket : BasePacket public EFT.Interactive.EExfiltrationStatus Command; public List QueuedPlayers; - public UpdateExfiltrationPointPacket() : base(nameof(UpdateExfiltrationPointPacket)) {} + public UpdateExfiltrationPointPacket() : base(nameof(UpdateExfiltrationPointPacket)) { } public override byte[] Serialize() { var ms = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(ms); + using BinaryWriter writer = new(ms); WriteHeader(writer); writer.Write(PointName); writer.Write((byte)Command); @@ -32,7 +32,7 @@ public override byte[] Serialize() public override ISITPacket Deserialize(byte[] bytes) { - using BinaryReader reader = new BinaryReader(new MemoryStream(bytes)); + using BinaryReader reader = new(new MemoryStream(bytes)); ReadHeader(reader); PointName = reader.ReadString(); Command = (EFT.Interactive.EExfiltrationStatus)reader.ReadByte(); @@ -46,7 +46,13 @@ public override ISITPacket Deserialize(byte[] bytes) } public override void Process() { - var point = ExfiltrationControllerClass.Instance.ExfiltrationPoints.First(x => x.Settings.Name == PointName); + if (ExfiltrationControllerClass.Instance == null) + return; + + if (ExfiltrationControllerClass.Instance.ExfiltrationPoints == null) + return; + + var point = ExfiltrationControllerClass.Instance.ExfiltrationPoints.FirstOrDefault(x => x.Settings != null && x.Settings.Name == PointName); if (point == null) { return; diff --git a/Source/Coop/Player/Health/RestoreBodyPartPatch.cs b/Source/Coop/Player/Health/RestoreBodyPartPatch.cs index c559bf86d..b29fc5ea7 100644 --- a/Source/Coop/Player/Health/RestoreBodyPartPatch.cs +++ b/Source/Coop/Player/Health/RestoreBodyPartPatch.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Reflection; using UnityEngine; -using static AHealthController; namespace StayInTarkov.Coop.Player.Health { @@ -59,81 +58,5 @@ PlayerHealthController __instance //Logger.LogInfo(json); GameClient.SendData(restoreBodyPartPacket.Serialize()); } - - - public override void Replicated(EFT.Player player, Dictionary dict) - { - RestoreBodyPartPacket restoreBodyPartPacket = new(); - restoreBodyPartPacket.Deserialize((byte[])dict["data"]); - - if (HasProcessed(GetType(), player, restoreBodyPartPacket)) - return; - - if (player.HealthController != null && player.HealthController.IsAlive) - { - Logger.LogDebug("Replicated: Calling RestoreBodyPart"); - Logger.LogDebug(restoreBodyPartPacket.ToJson()); - - if (dict == null) - { - Logger.LogError($"Dictionary packet is null?"); - return; - - } - //Logger.LogInfo(dict.ToJson()); - - var bodyPart = (EBodyPart)Enum.Parse(typeof(EBodyPart), restoreBodyPartPacket.BodyPart, true); - var bodyPartState = GetBodyPartDictionary(player)[bodyPart]; - - if (bodyPartState == null) - { - Logger.LogError($"Could not retreive {player.ProfileId}'s Health State for Body Part {restoreBodyPartPacket.BodyPart}"); - return; - } - - if (bodyPartState.IsDestroyed) - { - bodyPartState.IsDestroyed = false; - var healthPenalty = restoreBodyPartPacket.HealthPenalty + (1f - restoreBodyPartPacket.HealthPenalty) * player.Skills.SurgeryReducePenalty; - Logger.LogDebug("RestoreBodyPart::HealthPenalty::" + healthPenalty); - bodyPartState.Health = new HealthValue(1f, Mathf.Max(1f, Mathf.Ceil(bodyPartState.Health.Maximum * healthPenalty)), 0f); - - player.ExecuteSkill(new Action(player.Skills.SurgeryAction.Complete)); - player.UpdateSpeedLimitByHealth(); - } - } - } - - private Dictionary GetBodyPartDictionary(EFT.Player player) - { - try - { - var bodyPartDict - = ReflectionHelpers.GetFieldOrPropertyFromInstance> - (player.PlayerHealthController, "Dictionary_0", false); - if (bodyPartDict == null) - { - Logger.LogError($"Could not retreive {player.ProfileId}'s Health State Dictionary"); - return null; - } - //Logger.LogInfo(bodyPartDict.ToJson()); - return bodyPartDict; - } - catch (Exception) - { - - var field = ReflectionHelpers.GetFieldFromType(player.PlayerHealthController.GetType(), "Dictionary_0"); - Logger.LogError(field); - var type = field.DeclaringType; - Logger.LogError(type); - var val = field.GetValue(player.PlayerHealthController); - Logger.LogError(val); - var valType = field.GetValue(player.PlayerHealthController).GetType(); - Logger.LogError(valType); - } - - return null; - } - } } diff --git a/Source/Coop/Player/PlayerInventoryController_RechamberWeapon_Patch.cs b/Source/Coop/Player/PlayerInventoryController_RechamberWeapon_Patch.cs index 083354a91..3e2536a8f 100644 --- a/Source/Coop/Player/PlayerInventoryController_RechamberWeapon_Patch.cs +++ b/Source/Coop/Player/PlayerInventoryController_RechamberWeapon_Patch.cs @@ -48,53 +48,5 @@ public static void PostPatch(InventoryControllerClass __instance, Weapon weapon) var serialized = itemPacket.Serialize(); GameClient.SendData(serialized); } - - public override async void Replicated(EFT.Player player, Dictionary dict) - { - Logger.LogInfo($"PlayerInventoryController_RechamberWeapon_Patch.Replicated"); - - if (!dict.ContainsKey("data")) - return; - - ItemPlayerPacket itemPacket = new(player.ProfileId, null, null, dict["m"].ToString()); - itemPacket.Deserialize((byte[])dict["data"]); - - if (HasProcessed(GetType(), player, itemPacket)) - return; - - if (player.IsYourPlayer) - { - if (ItemFinder.TryFindItemController(player.ProfileId, out TraderControllerClass itemController)) - { - if (ItemFinder.TryFindItem(itemPacket.ItemId, out Item item)) - { - if (item is Weapon weapon) - { - CallLocally.Add(player.ProfileId); - Logger.LogInfo($"PlayerInventoryController_RechamberWeapon_Patch.Replicated. Calling RechamberWeapon ({itemPacket.ItemId})"); - itemController.RechamberWeapon(weapon); - } - } - else - { - Logger.LogError($"PlayerInventoryController_RechamberWeapon_Patch.Replicated. Unable to find Inventory Controller item {itemPacket.ItemId}"); - } - } - else - { - Logger.LogError("PlayerInventoryController_RechamberWeapon_Patch.Replicated. Unable to find Item Controller"); - } - } - else - { - var firearmsAnimator = player.HandsController.FirearmsAnimator; - if (firearmsAnimator != null) - { - firearmsAnimator.Rechamber(true); - await Task.Delay(250); - firearmsAnimator.Rechamber(false); - } - } - } } } \ No newline at end of file diff --git a/Source/Coop/Player/Player_Gesture_Patch.cs b/Source/Coop/Player/Player_Gesture_Patch.cs deleted file mode 100644 index bd4282bc7..000000000 --- a/Source/Coop/Player/Player_Gesture_Patch.cs +++ /dev/null @@ -1,74 +0,0 @@ -using StayInTarkov.Coop.Web; -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace StayInTarkov.Coop.Player -{ - internal class Player_Gesture_Patch : ModuleReplicationPatch - { - public static List CallLocally = new(); - public override Type InstanceType => typeof(EFT.Player); - public override string MethodName => "Gesture"; - - protected override MethodBase GetTargetMethod() - { - var method = ReflectionHelpers.GetMethodForType(InstanceType, "vmethod_3"); - return method; - } - - [PatchPrefix] - public static bool PrePatch(EFT.Player __instance) - { - var result = false; - if (CallLocally.Contains(__instance.ProfileId)) - result = true; - - return result; - } - - [PatchPostfix] - public static void PostPatch( - EFT.Player __instance, - EGesture gesture - ) - { - var player = __instance; - - if (CallLocally.Contains(player.ProfileId)) - { - CallLocally.Remove(player.ProfileId); - return; - } - - Dictionary dictionary = new(); - dictionary.Add("g", gesture.ToString()); - dictionary.Add("m", "Gesture"); - AkiBackendCommunicationCoop.PostLocalPlayerData(player, dictionary); - } - - - public override void Replicated(EFT.Player player, Dictionary dict) - { - if (HasProcessed(GetType(), player, dict)) - return; - - if (CallLocally.Contains(player.ProfileId)) - return; - - try - { - CallLocally.Add(player.ProfileId); - if (Enum.TryParse(dict["g"].ToString(), out var g)) - { - player.vmethod_3(g); - } - } - catch (Exception e) - { - Logger.LogInfo(e); - } - - } - } -} diff --git a/Source/Coop/Player/Player_SwitchHeadLights_Patch.cs b/Source/Coop/Player/Player_SwitchHeadLights_Patch.cs index 7971b08b6..dded2c82b 100644 --- a/Source/Coop/Player/Player_SwitchHeadLights_Patch.cs +++ b/Source/Coop/Player/Player_SwitchHeadLights_Patch.cs @@ -45,18 +45,5 @@ public static void PatchPostfix(EFT.Player __instance, bool togglesActive, bool }; AkiBackendCommunicationCoop.PostLocalPlayerData(__instance, dictionary); } - - public override void Replicated(EFT.Player player, Dictionary dict) - { - if (HasProcessed(GetType(), player, dict)) - return; - - var togglesActive = bool.Parse(dict["togglesActive"].ToString()); - var changesState = bool.Parse(dict["changesState"].ToString()); - - CallLocally.Add(player.ProfileId); - Logger.LogDebug($"Player_SwitchHeadLights_Patch:Replicated. Calling SwitchHeadLights(togglesActive: {togglesActive}, changesState: {changesState})"); - player.SwitchHeadLights(togglesActive, changesState); - } } } diff --git a/Source/Coop/Player/Proceed/Player_TryProceed_Patch.cs b/Source/Coop/Player/Proceed/Player_TryProceed_Patch.cs index 9aabf4c5b..3f1ce76b7 100644 --- a/Source/Coop/Player/Proceed/Player_TryProceed_Patch.cs +++ b/Source/Coop/Player/Proceed/Player_TryProceed_Patch.cs @@ -33,10 +33,5 @@ public static void PostPatch(EFT.Player __instance, Item item, bool scheduled) PlayerTryProceedPacket tryProceedPacket = new PlayerTryProceedPacket(__instance.ProfileId, item, scheduled); GameClient.SendData(tryProceedPacket.Serialize()); } - - public override void Replicated(EFT.Player player, Dictionary dict) - { - // Leave empty. Processed via the Packet itself. - } } } \ No newline at end of file diff --git a/Source/Coop/Player/TraderControllerClassHasForeignEventsPatch.cs b/Source/Coop/Player/TraderControllerClassHasForeignEventsPatch.cs index 2bf504ca5..bb9de4db4 100644 --- a/Source/Coop/Player/TraderControllerClassHasForeignEventsPatch.cs +++ b/Source/Coop/Player/TraderControllerClassHasForeignEventsPatch.cs @@ -13,10 +13,6 @@ internal sealed class TraderControllerClassHasForeignEventsPatch : ModuleReplica public override string MethodName => "HasForeignEvents"; - public override void Replicated(EFT.Player player, Dictionary dict) - { - } - protected override MethodBase GetTargetMethod() { return ReflectionHelpers.GetMethodForType(InstanceType, MethodName); diff --git a/Source/Coop/Players/CoopPlayer.cs b/Source/Coop/Players/CoopPlayer.cs index e8490ad91..3e12d0b78 100644 --- a/Source/Coop/Players/CoopPlayer.cs +++ b/Source/Coop/Players/CoopPlayer.cs @@ -16,6 +16,8 @@ using StayInTarkov.Coop.NetworkPacket.Player; using StayInTarkov.Coop.NetworkPacket.Player.Health; using StayInTarkov.Coop.NetworkPacket.Player.Proceed; +using StayInTarkov.FlatBuffers; + //using StayInTarkov.Core.Player; using StayInTarkov.Networking; using System; @@ -359,7 +361,7 @@ public IEnumerator ReceiveDamageFromServerCR(DamageInfo damageInfo, EBodyPart bo } ProceduralWeaponAnimation.ForceReact.AddForce(Mathf.Sqrt(absorbedDamage) / 10, handsShake, cameraShake); - if (FPSCamera.Instance.EffectsController.TryGetComponent(out FastBlur fastBlur)) + if (CameraClass.Instance.EffectsController.TryGetComponent(out FastBlur fastBlur)) { fastBlur.enabled = true; fastBlur.Hit(MovementContext.PhysicalConditionIs(EPhysicalCondition.OnPainkillers) ? absorbedDamage : bodyPartType == EBodyPart.Head ? absorbedDamage * 6 : absorbedDamage * 3); @@ -454,7 +456,7 @@ public override void Say(EPhraseTrigger @event, bool demand = false, float delay //GameClient.SendData(sayPacket.Serialize()); } - public override void OnPhraseTold(EPhraseTrigger @event, TaggedClip clip, TagBank bank, Speaker speaker) + public override void OnPhraseTold(EPhraseTrigger @event, TaggedClip clip, TagBank bank, PhraseSpeakerClass speaker) { base.OnPhraseTold(@event, clip, bank, speaker); @@ -491,11 +493,6 @@ public override void OnDestroy() base.OnDestroy(); } - public virtual void ReceivePlayerStatePacket(PlayerStatePacket playerStatePacket) - { - - } - protected virtual void Interpolate() { @@ -572,7 +569,7 @@ public override string ToString() private Vector2 LastRotationSent = Vector2.zero; private readonly Dictionary PendingArmorUpdates = []; - public override void Proceed(bool withNetwork, Callback callback, bool scheduled = true) + public override void Proceed(bool withNetwork, Callback callback, bool scheduled = true) { // Protection if (this is CoopPlayerClient) @@ -592,7 +589,6 @@ public override void Proceed(bool withNetwork, Callback callback, b } } - public override void Proceed(FoodClass foodDrink, float amount, Callback callback, int animationVariant, bool scheduled = true) { @@ -666,19 +662,20 @@ public override void Proceed(MedsClass meds, EBodyPart bodyPart, Callback callback, bool scheduled = true) + + public override void Proceed(GrenadeClass throwWeap, Callback callback, bool scheduled = true) { BepInLogger.LogDebug($"{nameof(CoopPlayer)}:{nameof(Proceed)}:{nameof(throwWeap)}:IGrenadeQuickUseController"); base.Proceed(throwWeap, callback, scheduled); } - public override void Proceed(GrenadeClass throwWeap, Callback callback, bool scheduled = true) + public override void Proceed(GrenadeClass throwWeap, Callback callback, bool scheduled = true) { BepInLogger.LogDebug($"{nameof(CoopPlayer)}:{nameof(Proceed)}:{nameof(throwWeap)}:IThrowableCallback"); //base.Proceed(throwWeap, callback, scheduled); Func controllerFactory = () => GrenadeController.smethod_8(this, throwWeap); - new Process(this, controllerFactory, throwWeap).method_0(null, callback, scheduled); + new Process(this, controllerFactory, throwWeap).method_0(null, callback, scheduled); PlayerProceedGrenadePacket packet = new PlayerProceedGrenadePacket(ProfileId, throwWeap.Id, scheduled); GameClient.SendData(packet.Serialize()); @@ -751,17 +748,6 @@ public override void Proceed(KnifeComponent knife, Callback(Item item, Callback callback, bool scheduled = true) - { - base.Proceed(item, callback, scheduled); - - BepInLogger.LogDebug($"{nameof(CoopPlayer)}:{nameof(Proceed)}"); - - Func controllerFactory = () => UsableItemController.smethod_5(this, item); - new Process(this, controllerFactory, item, fastHide: true).method_0(null, callback, scheduled); - } - - public override void DropCurrentController(Action callback, bool fastDrop, Item nextControllerItem = null) { BepInLogger.LogDebug($"{nameof(CoopPlayer)}:{nameof(DropCurrentController)}"); @@ -890,6 +876,15 @@ public override void vmethod_1(WorldInteractiveObject door, InteractionResult in } + public override void vmethod_3(EGesture gesture) + { + base.vmethod_3(gesture); + + PlayerGesturePacket packet = new PlayerGesturePacket(); + packet.Gesture = gesture; + GameClient.SendData(packet.Serialize()); + } + void Awake() { @@ -941,26 +936,6 @@ void CreateDogtag() } } - public void ProcessModuleReplicationPatch(Dictionary packet) - { - if (!packet.ContainsKey("m")) - return; - - var method = packet["m"].ToString(); - - if (!ModuleReplicationPatch.Patches.ContainsKey(method)) - return; - - var patch = ModuleReplicationPatch.Patches[method]; - if (patch != null) - { - patch.Replicated(this, packet); - return; - } - - - } - public void ReceiveArmorDamageFromServer(Dictionary pendingArmorUpdates) { List putOnArmors = []; diff --git a/Source/Coop/Players/CoopPlayerClient.cs b/Source/Coop/Players/CoopPlayerClient.cs index 8f494b9f5..70f189e39 100644 --- a/Source/Coop/Players/CoopPlayerClient.cs +++ b/Source/Coop/Players/CoopPlayerClient.cs @@ -2,23 +2,27 @@ using Comfort.Common; using Diz.LanguageExtensions; using EFT; +using EFT.CameraControl; using EFT.Interactive; using EFT.InventoryLogic; +using GPUInstancer; +using StayInTarkov.Configuration; using StayInTarkov.Coop.Components.CoopGameComponents; using StayInTarkov.Coop.Controllers; using StayInTarkov.Coop.Controllers.HandControllers; using StayInTarkov.Coop.Matchmaker; using StayInTarkov.Coop.NetworkPacket; +using StayInTarkov.Coop.NetworkPacket.FlatBuffers; using StayInTarkov.Coop.NetworkPacket.Player; +using StayInTarkov.Coop.NetworkPacket.Player.Health; using StayInTarkov.Coop.NetworkPacket.Player.Proceed; +using StayInTarkov.FlatBuffers; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Security.AccessControl; using UnityEngine; using UnityEngine.Networking; -using static AHealthController; -using static UnityEngine.SendMouseEvents; namespace StayInTarkov.Coop.Players { @@ -26,33 +30,19 @@ public class CoopPlayerClient : CoopPlayer { public override ManualLogSource BepInLogger { get; } = BepInEx.Logging.Logger.CreateLogSource(nameof(CoopPlayerClient)); - public PlayerStatePacket LastState { get; set; }// = new PlayerStatePacket(); - public PlayerStatePacket NewState { get; set; }// = new PlayerStatePacket(); + public PlayerState LastState { get; set; } + public PlayerState NewState { get; set; } - public ConcurrentQueue ReplicatedPostProceedData { get; } = new (); + public ConcurrentQueue ReplicatedPostProceedData { get; } = new(); protected AbstractHealth NetworkHealthController => base.HealthController as AbstractHealth; - //public override void InitVoip(EVoipState voipState) - //{ - // //base.InitVoip(voipState); - // SoundSettings settings = Singleton.Instance.Sound.Settings; - //} - - //public override void Move(Vector2 direction) - //{ - // //base.Move(direction); - //} - public override void OnDead(EDamageType damageType) { - //if (damageType == EDamageType.Fall) - // return; #if DEBUG BepInLogger.LogDebug($"{nameof(CoopPlayerClient)}:{nameof(OnDead)}:{damageType}"); #endif base.OnDead(damageType); - //Singleton.Instance.UnsubscribeProtagonist(); } public override ApplyShot ApplyShot(DamageInfo damageInfo, EBodyPart bodyPartType, EBodyPartColliderType colliderType, EArmorPlateCollider armorPlateCollider, ShotId shotId) @@ -91,21 +81,9 @@ public override void KillMe(EBodyPartColliderType colliderType, float damage) BepInLogger.LogDebug($"{nameof(CoopPlayerClient)}:{nameof(KillMe)}"); } - DateTime? LastRPSP = null; - - public override void ReceivePlayerStatePacket(PlayerStatePacket playerStatePacket) + public void ReceivePlayerStatePacket(PlayerState state) { - NewState = playerStatePacket; - //BepInLogger.LogInfo($"{nameof(ReceivePlayerStatePacket)}:Packet took {DateTime.Now - new DateTime(long.Parse(NewState.TimeSerializedBetter))}."); - - //BepInLogger.LogInfo(NewState.ToJson()); - - if (LastRPSP == null) - LastRPSP = DateTime.Now; - - //BepInLogger.LogInfo($"Time between {nameof(ReceivePlayerStatePacket)} {DateTime.Now - LastRPSP.Value}"); - - LastRPSP = DateTime.Now; + NewState = state; } public Queue ReceivedPackets = new Queue(); @@ -132,131 +110,43 @@ void Update() Proceed(meds, medsPacket.BodyPart, null, medsPacket.AnimationVariant, medsPacket.Scheduled); } } - //if (packet is PlayerPostProceedDataSyncPacket postProceedDataSyncPacket) - //{ - // BepInLogger.LogDebug($"{nameof(Update)}:{nameof(ReceivedPackets)}:Process:{packet.GetType().Name}"); - - // if (ItemFinder.TryFindItem(postProceedDataSyncPacket.ItemId, out Item item)) - // { - // BepInLogger.LogDebug($"{nameof(Update)}:{nameof(ReceivedPackets)}:Process:{packet.GetType().Name}:Item:{item}"); - // BepInLogger.LogDebug($"{nameof(Update)}:{nameof(ReceivedPackets)}:Process:{packet.GetType().Name}:packet:{packet}"); - // var shouldRemoveItem = false; - // if (item is MedsClass meds) - // { - // if (meds.MedKitComponent != null) - // { - // meds.MedKitComponent.HpResource = postProceedDataSyncPacket.NewValue; - // shouldRemoveItem = (meds.MedKitComponent.HpResource <= 0); - // } - // // one time use - // else - // { - // shouldRemoveItem = true; - // } - // } - // if (item is FoodClass food) - // { - // if (food.FoodDrinkComponent != null) - // { - // food.FoodDrinkComponent.HpPercent = postProceedDataSyncPacket.NewValue; - // shouldRemoveItem = (food.FoodDrinkComponent.HpPercent <= 0); - - // } - // // one time use - // else - // { - // shouldRemoveItem = true; - // } - // } - - // item.RaiseRefreshEvent(); - - // //base.DropCurrentController(() => { }, false, null); - // //var medsController = HandsController as MedsController; - // //if (medsController != null) - // { - // if (shouldRemoveItem) - // { - // BepInLogger.LogDebug($"Discard Requested {item}"); - // var discardAttempt = ItemMovementHandler.Discard(item, this._inventoryController, true, false); - // if (discardAttempt.Succeeded) - // RemoveItem(item); - // else - // { - // BepInLogger.LogError($"Unable to Discard {item}. Reason: {discardAttempt.Error}"); - // } - // } - // else - // { - // BepInLogger.LogDebug($"Not Discard {item}. Reason: Not Requested"); - // } - // } - - - // } - //} } - - + // FIXME(belette) only do this when a new network update has been received (use high watermark timestamp) + // since Update() happens more often than network updates. // Update the Health parts of this character using the packets from the Player State UpdatePlayerHealthByPlayerState(); } private void UpdatePlayerHealthByPlayerState() { - if (NewState == null) - return; - - if (NewState.PlayerHealth == null) + if (NewState.Empty()) + { return; + } - var bodyPartDictionary = GetBodyPartDictionary(this); + var bodyPartDictionary = PlayerHealthController?.Dictionary_0; if (bodyPartDictionary == null) { - BepInLogger.LogError($"{nameof(CoopPlayerClient)}:Unable to obtain BodyPartDictionary"); + BepInLogger.LogError($"{nameof(CoopPlayerClient)}: could not retrieve bodyPartDictionary"); return; } - foreach (var bodyPartPacket in NewState.PlayerHealth.BodyParts) + foreach (EBodyPart part in Enum.GetValues(typeof(EBodyPart))) { - if (bodyPartPacket.BodyPart == EBodyPart.Common) + if (part == EBodyPart.Common) continue; - if (bodyPartDictionary.ContainsKey(bodyPartPacket.BodyPart)) + if (bodyPartDictionary.ContainsKey(part)) { //BepInLogger.LogInfo($"{nameof(Update)} set bodyPart current {bodyPartPacket.ToJson()}"); - bodyPartDictionary[bodyPartPacket.BodyPart].Health.Current = bodyPartPacket.Current; + bodyPartDictionary[part].Health.Current = NewState.BodyPartsHealth.Value.Parts((byte)part).Current; } else { - //BepInLogger.LogError($"{nameof(CoopPlayerClient)}:Unable to find {bodyPartPacket.BodyPart} in BodyPartDictionary {bodyPartDictionary.Keys.ToJson()}"); - } - } - - } - - private Dictionary GetBodyPartDictionary(EFT.Player player) - { - try - { - var bodyPartDict - = ReflectionHelpers.GetFieldOrPropertyFromInstance> - (player.PlayerHealthController, "Dictionary_0", false); - if (bodyPartDict == null) - { - Logger.LogError($"Could not retreive {player.ProfileId}'s Health State Dictionary"); - return null; + BepInLogger.LogError($"{nameof(CoopPlayerClient)}: could not find {part} in dictionary {bodyPartDictionary.Keys.ToJson()}"); } - //Logger.LogInfo(bodyPartDict.ToJson()); - return bodyPartDict; } - catch (Exception) - { - - } - - return null; } new void LateUpdate() @@ -276,12 +166,14 @@ var bodyPartDict } ComplexLateUpdate(EUpdateQueue.Update, DeltaTime); - if (LastState == null) + if (LastState.Empty()) + { return; + } if (LastState.LinearSpeed > 0.25) { - Move(LastState.InputDirection); + Move(new Vector2(LastState.InputDirection.Value.X, LastState.InputDirection.Value.Y)); } /// @@ -298,10 +190,10 @@ var bodyPartDict { base.FixedUpdate(); - if (FPSCamera.Instance == null) + if (CameraClass.Instance == null) return; - var mainCamera = FPSCamera.Instance.Camera; + var mainCamera = CameraClass.Instance.Camera; if (mainCamera == null) { return; @@ -312,14 +204,14 @@ var bodyPartDict var headPosition = this.MainParts[BodyPartType.head].Position; var dir = (headPosition - mainCamera.transform.position); var distanceFromCamera = Vector3.Distance(startPosition, headPosition); - + RaycastHit hit; // Does the ray intersect any objects excluding the player layer if (Physics.Raycast(startPosition, dir, out hit, Mathf.Infinity, LayerMaskClass.LowPolyColliderLayerMask)) { _isTeleporting = false; - foreach (var c in this._hitColliders) + foreach (var c in this._hitColliders) { if (hit.collider == c) return; @@ -348,13 +240,13 @@ var bodyPartDict // _raycastHitCube.GetComponent().enabled = false; //} //_raycastHitCube.transform.position = hit.point; - + // If the guy is further than 40m away. Use the Teleportation system. - if (NewState != null && distanceFromCamera > 40) + if (!NewState.Empty() && distanceFromCamera > PluginConfigSettings.Instance.CoopSettings.BotTeleportDistance) { - Teleport(NewState.Position); - this.Position = NewState.Position; - this.Rotation = NewState.Rotation; + this.Position = NewState.Position.Value.Unity(); + this.Rotation = NewState.Rotation.Value.Unity(); + Teleport(this.Position); //BepInLogger.LogDebug($"Teleporting {ProfileId}"); _isTeleporting = true; } @@ -369,7 +261,7 @@ var bodyPartDict //GameObject.Destroy(_raycastHitCube); } - + } bool _isTeleporting = false; @@ -377,7 +269,7 @@ var bodyPartDict public void InterpolateOrTeleport() { - if(!_isTeleporting) + if (!_isTeleporting) Interpolate(); } @@ -395,23 +287,23 @@ protected override void Interpolate() if (MovementContext == null) return; - if (NewState == null) + if (NewState.Empty()) return; - if (LastState == null) + if (LastState.Empty()) LastState = NewState; var InterpolationRatio = Time.deltaTime * 5; - Rotation = new Vector2(Mathf.LerpAngle(Yaw, NewState.Rotation.x, InterpolationRatio), Mathf.Lerp(Pitch, NewState.Rotation.y, InterpolationRatio)); + Rotation = new Vector2(Mathf.LerpAngle(Yaw, NewState.Rotation.Value.X, InterpolationRatio), Mathf.Lerp(Pitch, NewState.Rotation.Value.Y, InterpolationRatio)); - HeadRotation = Vector3.Lerp(HeadRotation, NewState.HeadRotation, InterpolationRatio); - ProceduralWeaponAnimation.SetHeadRotation(Vector3.Lerp(LastState.HeadRotation, NewState.HeadRotation, InterpolationRatio)); - MovementContext.PlayerAnimatorSetMovementDirection(Vector2.Lerp(LastState.MovementDirection, NewState.MovementDirection, Time.deltaTime)); - MovementContext.PlayerAnimatorSetDiscreteDirection(BSGDirectionalHelpers.ConvertToMovementDirection(NewState.MovementDirection)); + HeadRotation = Vector3.Lerp(HeadRotation, NewState.HeadRotation.Value.Unity(), InterpolationRatio); + ProceduralWeaponAnimation.SetHeadRotation(Vector3.Lerp(LastState.HeadRotation.Value.Unity(), NewState.HeadRotation.Value.Unity(), InterpolationRatio)); + MovementContext.PlayerAnimatorSetMovementDirection(Vector2.Lerp(LastState.MovementDirection.Value.Unity(), NewState.MovementDirection.Value.Unity(), Time.deltaTime)); + MovementContext.PlayerAnimatorSetDiscreteDirection(BSGDirectionalHelpers.ConvertToMovementDirection(NewState.MovementDirection.Value.Unity())); EPlayerState currentPlayerState = MovementContext.CurrentState.Name; - EPlayerState eplayerState = NewState.State; + EPlayerState eplayerState = (EPlayerState)NewState.State; if (eplayerState == EPlayerState.ClimbUp || eplayerState == EPlayerState.ClimbOver || eplayerState == EPlayerState.VaultingLanding || eplayerState == EPlayerState.VaultingFallDown) { @@ -436,7 +328,12 @@ protected override void Interpolate() MovementContext.IsInPronePose = true; } - Physical.SerializationStruct = NewState.Stamina; + Physical.SerializationStruct = new PhysicalStamina + { + StaminaExhausted = NewState.StaminaExhausted, + OxygenExhausted = NewState.OxygenExhausted, + HandsExhausted = NewState.HandsExhausted + }; MovementContext.SetTilt(Mathf.Round(NewState.Tilt)); // Round the float due to byte converting error... CurrentManagedState.SetStep(NewState.Step); MovementContext.PlayerAnimatorEnableSprint(NewState.IsSprinting); @@ -464,16 +361,16 @@ private void ApplyReplicatedMotion() if (MovementContext == null) return; - if (NewState == null) return; + if (NewState.Empty()) return; - if (LastState == null) return; + if (LastState.Empty()) return; - Vector3 lerpedMovement = Vector3.Lerp(MovementContext.TransformPosition, NewState.Position, Time.deltaTime * 1.33f); + Vector3 lerpedMovement = Vector3.Lerp(MovementContext.TransformPosition, NewState.Position.Value.Unity(), Time.deltaTime * 1.33f); CharacterController.Move((lerpedMovement + MovementContext.PlatformMotion) - MovementContext.TransformPosition, Time.deltaTime); if (!IsInventoryOpened && LastState.LinearSpeed > 0.25) { - Move(LastState.InputDirection); + Move(LastState.InputDirection.Value.Unity()); } } @@ -492,11 +389,10 @@ public override void OnSkillLevelChanged(AbstractSkill skill) { } - public override void OnWeaponMastered(MasterSkill masterSkill) + public override void OnWeaponMastered(MasterSkillClass masterSkill) { } - public override void StartInflictSelfDamageCoroutine() { } @@ -529,7 +425,7 @@ public override void DropCurrentController(Action callback, bool fastDrop, Item } BepInLogger.LogDebug($"{nameof(CoopPlayerClient)}:{nameof(DropCurrentController)}"); - + base.DropCurrentController(callback, fastDrop, nextControllerItem); @@ -560,54 +456,6 @@ public override void DropCurrentController(Action callback, bool fastDrop, Item } } - - public bool RemoveItem(Item item) - { - TraderControllerClass invController = this._inventoryController; - IOperationResult value; - Error error; - - if(item.Owner == null) - { - ReflectionHelpers.SetFieldOrPropertyFromInstance(item, "Owner", invController); - } - - try - { - if (item.StackObjectsCount > 1) - { - var sOperationResult = ItemMovementHandler.SplitToNowhere(item, 1, invController, invController, simulate: false); - value = sOperationResult.Value; - error = sOperationResult.Error; - } - else - { - global::SOperationResult12 sOperationResult2 = ItemMovementHandler.Discard(item, invController, false, false); - value = sOperationResult2.Value; - error = sOperationResult2.Error; - } - if (error != null) - { - BepInLogger.LogError($"Couldn't remove item: {error}"); - return false; - } - if (item.Owner == null) - { - ReflectionHelpers.SetFieldOrPropertyFromInstance(item, "Owner", invController); - } - value.RaiseEvents(invController, CommandStatus.Begin); - value.RaiseEvents(invController, CommandStatus.Succeed); - } - catch (Exception) - { - - } - return true; - } - - - - public override void ReceiveSay(EPhraseTrigger trigger, int index, ETagStatus mask, bool aggressive) { BepInLogger.LogDebug($"{nameof(ReceiveSay)}({trigger},{mask})"); @@ -643,7 +491,7 @@ public override void Proceed(KnifeComponent knife, Callback process = new Process(this, controllerFactory, knife.Item, fastHide: true, AbstractProcess.Completion.Sync, AbstractProcess.Confirmation.Succeed, skippable: false); Action confirmCallback = delegate { - + }; process.method_0(delegate (IResult result) { @@ -654,18 +502,19 @@ public override void Proceed(KnifeComponent knife, Callback callback, bool scheduled = true) + public override void Proceed(GrenadeClass throwWeap, Callback callback, bool scheduled = true) { Func controllerFactory = () => GrenadeController.smethod_8(this, throwWeap); - new Process(this, controllerFactory, throwWeap).method_0(null, callback, scheduled); + new Process(this, controllerFactory, throwWeap).method_0(null, callback, scheduled); } - public override void Proceed(bool withNetwork, Callback callback, bool scheduled = true) + public override void Proceed(bool withNetwork, Callback callback, bool scheduled = true) { Func controllerFactory = () => EmptyHandsController.smethod_5(this); - new Process(this, controllerFactory, null).method_0(null, callback, scheduled); + new Process(this, controllerFactory, null).method_0(null, callback, scheduled); base.Proceed(withNetwork, callback, scheduled); } + public override void Proceed(FoodClass foodDrink, float amount, Callback callback, int animationVariant, bool scheduled = true) { Func controllerFactory = () => MedsController.smethod_5(this, foodDrink, EBodyPart.Head, amount, animationVariant); @@ -688,5 +537,13 @@ public override void vmethod_1(WorldInteractiveObject door, InteractionResult in CurrentManagedState.ExecuteDoorInteraction(door, interactionResult, null, this); } } + + public override void vmethod_3(EGesture gesture) + { + if (!HandsController.IsInInteractionStrictCheck()) + { + HandsController.ShowGesture(gesture); + } + } } } diff --git a/Source/Coop/Players/PlayerFactory.cs b/Source/Coop/Players/PlayerFactory.cs index 3fe2fd217..6c82abf01 100644 --- a/Source/Coop/Players/PlayerFactory.cs +++ b/Source/Coop/Players/PlayerFactory.cs @@ -20,7 +20,7 @@ internal class PlayerFactory { public static QuestController GetQuestController(EFT.Profile profile, InventoryControllerClass inventoryController) { - var questController = new QuestController(profile, inventoryController, null, false); + var questController = new QuestController(profile, inventoryController, StayInTarkovHelperConstants.BackEndSession, true); questController.Init(); questController.Run(); return questController; @@ -84,8 +84,8 @@ public static async Task // Cant use ObservedPlayerMode, it causes the player to fall through the floor and die , BackendConfigManager.Config.CharacterController.ObservedPlayerMode //, BackendConfigManager.Config.CharacterController.ClientPlayerMode - , () => Singleton.Instance.Control.Settings.MouseSensitivity - , () => Singleton.Instance.Control.Settings.MouseAimingSensitivity + , () => Singleton.Instance.Control.Settings.MouseSensitivity + , () => Singleton.Instance.Control.Settings.MouseAimingSensitivity , FilterCustomizationClass.Default , null , isYourPlayer: false diff --git a/Source/Coop/SITGameModes/CoopSITGame.cs b/Source/Coop/SITGameModes/CoopSITGame.cs index a078e015b..a19de8a54 100644 --- a/Source/Coop/SITGameModes/CoopSITGame.cs +++ b/Source/Coop/SITGameModes/CoopSITGame.cs @@ -30,7 +30,6 @@ using StayInTarkov.Coop.Components.CoopGameComponents; using StayInTarkov.Coop.FreeCamera; using StayInTarkov.Coop.Matchmaker; -using StayInTarkov.Coop.NetworkPacket.Player; using StayInTarkov.Coop.NetworkPacket.Raid; using StayInTarkov.Coop.NetworkPacket.World; using StayInTarkov.Coop.Players; @@ -46,7 +45,6 @@ using UnityEngine; using UnityEngine.LowLevel; using UnityEngine.PlayerLoop; -using UnityEngine.Profiling; namespace StayInTarkov.Coop.SITGameModes { @@ -148,11 +146,16 @@ InputTree inputTree //location.OfflineOldSpawn = true; //location.OldSpawn = true; - CoopSITGame coopGame = + CoopSITGame coopGame = smethod_0(inputTree, profile, backendDateTime, insurance, menuUI, commonUI, preloaderUI, gameUI, location, timeAndWeather, wavesSettings, dateTime , callback, fixedDeltaTime, updateQueue, backEndSession, new TimeSpan?(sessionTime)); + // --------------------------------------------------------------------------------- + // Create Coop Game Component + Logger.LogDebug($"{nameof(Create)}:Running {nameof(coopGame.CreateCoopGameComponent)}"); + coopGame.CreateCoopGameComponent(); + #if DEBUG Logger.LogDebug($"DEBUG:{nameof(backendDateTime)}:{backendDateTime.ToJson()}"); #endif @@ -170,7 +173,7 @@ InputTree inputTree coopGame.wavesSpawnScenario_0 = WavesSpawnScenario.smethod_0( coopGame.gameObject , waves - , new Action(coopGame.PBotsController.ActivateBotsByWave) + , new Action(coopGame.PBotsController.ActivateBotsByWave) , location); // --------------------------------------------------------------------------------- @@ -184,19 +187,13 @@ InputTree inputTree // Setup ISITGame Singleton Singleton.Create(coopGame); - // --------------------------------------------------------------------------------- - // Create Coop Game Component - Logger.LogDebug($"{nameof(Create)}:Running {nameof(coopGame.CreateCoopGameComponent)}"); - coopGame.CreateCoopGameComponent(); - SITGameComponent.GetCoopGameComponent().LocalGameInstance = coopGame; - // --------------------------------------------------------------------------------- // Create GameClient(s) switch (SITMatchmaking.SITProtocol) { case ESITProtocol.RelayTcp: coopGame.GameClient = coopGame.GetOrAddComponent(); - + break; case ESITProtocol.PeerToPeerUdp: if (SITMatchmaking.IsServer) @@ -206,46 +203,35 @@ InputTree inputTree break; default: throw new Exception("Unknown SIT Protocol used!"); - + } return coopGame; } - public void CreateCoopGameComponent() + void OnDestroy() { - //var coopGameComponent = SITGameComponent.GetCoopGameComponent(); - //if (coopGameComponent != null) - //{ - // Destroy(coopGameComponent); - //} + Logger.LogDebug("OnDestroy()"); + Singleton.Instance.AfterGameStarted -= Instance_AfterGameStarted; - if (CoopPatches.CoopGameComponentParent != null) - { - Destroy(CoopPatches.CoopGameComponentParent); - CoopPatches.CoopGameComponentParent = null; - } + Comfort.Common.Singleton.TryRelease(this); + } - if (CoopPatches.CoopGameComponentParent == null) - { - CoopPatches.CoopGameComponentParent = new GameObject("CoopGameComponentParent"); - DontDestroyOnLoad(CoopPatches.CoopGameComponentParent); - } - CoopPatches.CoopGameComponentParent.AddComponent(); - var coopGameComponent = CoopPatches.CoopGameComponentParent.AddComponent(); - coopGameComponent.LocalGameInstance = this; + public void CreateCoopGameComponent() + { + var sitGameComponent = this.gameObject.AddComponent(); + this.gameObject.AddComponent(); - //coopGameComponent = gameWorld.GetOrAddComponent(); if (!string.IsNullOrEmpty(SITMatchmaking.GetGroupId())) { Logger.LogDebug($"{nameof(CreateCoopGameComponent)}:{SITMatchmaking.GetGroupId()}"); - coopGameComponent.ServerId = SITMatchmaking.GetGroupId(); - coopGameComponent.Timestamp = SITMatchmaking.GetTimestamp(); + sitGameComponent.ServerId = SITMatchmaking.GetGroupId(); + sitGameComponent.Timestamp = SITMatchmaking.GetTimestamp(); } else { - Destroy(coopGameComponent); - coopGameComponent = null; + Destroy(sitGameComponent); + sitGameComponent = null; Logger.LogError("========== ERROR = COOP ========================"); Logger.LogError("No Server Id found, Deleting Coop Game Component"); Logger.LogError("================================================"); @@ -254,16 +240,11 @@ public void CreateCoopGameComponent() if (SITMatchmaking.IsServer) { - StartCoroutine(GameTimerSync()); - StartCoroutine(TimeAndWeatherSync()); StartCoroutine(ArmoredTrainTimeSync()); } clientLoadingPingerCoroutine = StartCoroutine(ClientLoadingPinger()); - var friendlyAIJson = AkiBackendCommunication.Instance.GetJson($"/coop/server/friendlyAI/{SITGameComponent.GetServerId()}"); - Logger.LogDebug(friendlyAIJson); - //coopGame.FriendlyAIPMCSystem = JsonConvert.DeserializeObject(friendlyAIJson); } private IEnumerator ClientLoadingPinger() @@ -303,79 +284,6 @@ private IEnumerator DebugObjects() } } - private IEnumerator GameTimerSync() - { - var waitSeconds = new WaitForSeconds(10f); - - while (true) - { - yield return waitSeconds; - - if (!SITGameComponent.TryGetCoopGameComponent(out var coopGameComponent)) - yield break; - - if (GameTimer.StartDateTime.HasValue && GameTimer.SessionTime.HasValue) - { - //Dictionary raidTimerDict = new() - //{ - // { "serverId", coopGameComponent.ServerId }, - // { "m", "RaidTimer" }, - // { "sessionTime", (GameTimer.SessionTime - GameTimer.PastTime).Value.Ticks }, - //}; - //Networking.GameClient.SendData(raidTimerDict.ToJson()); - RaidTimerPacket packet = new RaidTimerPacket(); - packet.SessionTime = (GameTimer.SessionTime - GameTimer.PastTime).Value.Ticks; - Networking.GameClient.SendData(packet.Serialize()); - } - } - } - - private IEnumerator TimeAndWeatherSync() - { - var waitSeconds = new WaitForSeconds(15f); - - while (true) - { - yield return waitSeconds; - - if (!SITGameComponent.TryGetCoopGameComponent(out var coopGameComponent)) - yield break; - - Dictionary timeAndWeatherDict = new() - { - { "serverId", coopGameComponent.ServerId }, - { "m", "TimeAndWeather" } - }; - - if (GameDateTime != null) - timeAndWeatherDict.Add("GameDateTime", GameDateTime.Calculate().Ticks); - - var weatherController = WeatherController.Instance; - if (weatherController != null) - { - if (weatherController.CloudsController != null) - timeAndWeatherDict.Add("CloudDensity", weatherController.CloudsController.Density); - - var weatherCurve = weatherController.WeatherCurve; - if (weatherCurve != null) - { - timeAndWeatherDict.Add("Fog", weatherCurve.Fog); - timeAndWeatherDict.Add("LightningThunderProbability", weatherCurve.LightningThunderProbability); - timeAndWeatherDict.Add("Rain", weatherCurve.Rain); - timeAndWeatherDict.Add("Temperature", weatherCurve.Temperature); - timeAndWeatherDict.Add("WindDirection.x", weatherCurve.Wind.x); - timeAndWeatherDict.Add("WindDirection.y", weatherCurve.Wind.y); - timeAndWeatherDict.Add("TopWindDirection.x", weatherCurve.TopWind.x); - timeAndWeatherDict.Add("TopWindDirection.y", weatherCurve.TopWind.y); - } - - string packet = timeAndWeatherDict.ToJson(); - Logger.LogDebug(packet); - Networking.GameClient.SendData(packet); - } - } - } - private IEnumerator ArmoredTrainTimeSync() { var waitSeconds = new WaitForSeconds(30f); @@ -531,7 +439,7 @@ private async Task CreatePhysicalBot(Profile profile, Vector3 posit //public async Task CreatePhysicalPlayer(int playerId, Vector3 position, Quaternion rotation, string layerName, string prefix, EPointOfView pointOfView, Profile profile, bool aiControl, EUpdateQueue updateQueue, EFT.Player.EUpdateMode armsUpdateMode, EFT.Player.EUpdateMode bodyUpdateMode, CharacterControllerSpawner.Mode characterControllerMode, Func getSensitivity, Func getAimingSensitivity, IStatisticsManager statisticsManager, QuestControllerClass questController) //{ // profile.SetSpawnedInSession(value: false); - // return await LocalPlayer.Create(playerId, position, rotation, "Player", "", EPointOfView.FirstPerson, profile, aiControl: false, base.UpdateQueue, armsUpdateMode, EFT.Player.EUpdateMode.Auto, BackendConfigManager.Config.CharacterController.ClientPlayerMode, () => Singleton.Instance.Control.Settings.MouseSensitivity, () => Singleton.Instance.Control.Settings.MouseAimingSensitivity, new StatisticsManagerForPlayer1(), new FilterCustomizationClass(), questController, isYourPlayer: true); + // return await LocalPlayer.Create(playerId, position, rotation, "Player", "", EPointOfView.FirstPerson, profile, aiControl: false, base.UpdateQueue, armsUpdateMode, EFT.Player.EUpdateMode.Auto, BackendConfigManager.Config.CharacterController.ClientPlayerMode, () => Singleton.Instance.Control.Settings.MouseSensitivity, () => Singleton.Instance.Control.Settings.MouseAimingSensitivity, new StatisticsManagerForPlayer1(), new FilterCustomizationClass(), questController, isYourPlayer: true); //} public string InfiltrationPoint; @@ -550,7 +458,7 @@ public override void vmethod_1(float timeBeforeDeploy) base.vmethod_1(timeBeforeDeploy); } - public static void SendOrReceiveSpawnPoint(ref ISpawnPoint selectedSpawnPoint, SpawnPoints spawnPoints) + public static async Task SendOrReceiveSpawnPoint(ISpawnPoint selectedSpawnPoint, SpawnPoints spawnPoints) { var position = selectedSpawnPoint.Position; if (!SITMatchmaking.IsClient) @@ -583,15 +491,13 @@ public static void SendOrReceiveSpawnPoint(ref ISpawnPoint selectedSpawnPoint, S } }; Logger.LogInfo("Setting Spawn Point to " + position); - AkiBackendCommunication.Instance.PostJson("/coop/server/update", packet.ToJson()); - //var json = Request.Instance.GetJson($"/coop/server/spawnPoint/{CoopGameComponent.GetServerId()}"); - //Logger.LogInfo("Retreived Spawn Point " + json); + await AkiBackendCommunication.Instance.PostJsonAsync("/coop/server/update", packet.ToJson()); } else if (SITMatchmaking.IsClient) { if (PluginConfigSettings.Instance.CoopSettings.AllPlayersSpawnTogether) { - var json = AkiBackendCommunication.Instance.GetJson($"/coop/server/spawnPoint/{SITGameComponent.GetServerId()}"); + var json = await AkiBackendCommunication.Instance.GetJsonAsync($"/coop/server/spawnPoint/{SITGameComponent.GetServerId()}"); Logger.LogInfo("Retreived Spawn Point " + json); var retrievedPacket = json.ParseJsonTo>(); var x = float.Parse(retrievedPacket["x"].ToString()); @@ -601,7 +507,7 @@ public static void SendOrReceiveSpawnPoint(ref ISpawnPoint selectedSpawnPoint, S selectedSpawnPoint = spawnPoints.First(x => x.Position == teleportPosition); } } - //} + return selectedSpawnPoint; } internal Dictionary FriendlyPlayers { get; } = new Dictionary(); @@ -633,10 +539,10 @@ public static void SendOrReceiveSpawnPoint(ref ISpawnPoint selectedSpawnPoint, S public override async Task vmethod_2(int playerId, Vector3 position, Quaternion rotation, string layerName, string prefix, EPointOfView pointOfView, Profile profile, bool aiControl, EUpdateQueue updateQueue, EFT.Player.EUpdateMode armsUpdateMode, EFT.Player.EUpdateMode bodyUpdateMode, CharacterControllerSpawner.Mode characterControllerMode, Func getSensitivity, Func getAimingSensitivity, IStatisticsManager statisticsManager, AbstractQuestControllerClass questController, AbstractAchievementControllerClass achievementsController) { // Send Connect Command to Relay - switch(SITMatchmaking.SITProtocol) + switch (SITMatchmaking.SITProtocol) { case ESITProtocol.RelayTcp: - JObject j = new JObject(); + JObject j = new(); j.Add("serverId", SITGameComponent.GetServerId()); j.Add("profileId", profile.ProfileId); j.Add("connect", true); @@ -644,7 +550,7 @@ public override async Task vmethod_2(int playerId, Vector3 position GameClient.SendData(Encoding.UTF8.GetBytes(j.ToString())); break; } - + spawnPoints = SpawnPoints.CreateFromScene(DateTime.Now, Location_0.SpawnPointParams); @@ -659,8 +565,7 @@ public override async Task vmethod_2(int playerId, Vector3 position return null; } - - SendOrReceiveSpawnPoint(ref spawnPoint, spawnPoints); + spawnPoint = await SendOrReceiveSpawnPoint(spawnPoint, spawnPoints); Logger.LogDebug($"{nameof(vmethod_2)}:Creating Owner CoopPlayer"); var myPlayer = await CoopPlayer @@ -677,8 +582,8 @@ public override async Task vmethod_2(int playerId, Vector3 position , armsUpdateMode , EFT.Player.EUpdateMode.Auto , BackendConfigManager.Config.CharacterController.ClientPlayerMode - , () => Singleton.Instance.Control.Settings.MouseSensitivity - , () => Singleton.Instance.Control.Settings.MouseAimingSensitivity + , () => Singleton.Instance.Control.Settings.MouseSensitivity + , () => Singleton.Instance.Control.Settings.MouseAimingSensitivity , new FilterCustomizationClass() , null // Let the CoopPlayer Create handle this , null // Let the CoopPlayer Create handle this @@ -714,6 +619,9 @@ public override async Task vmethod_2(int playerId, Vector3 position private async Task WaitForPlayersToSpawn() { + if (SITMatchmaking.IsServer) + _ = SITGameModeHelpers.UpdateRaidStatusAsync(SITMPRaidStatus.WaitingForPlayers); + if (SITMatchmaking.TimeHasComeScreenController != null) { SITMatchmaking.TimeHasComeScreenController.ChangeStatus($"Session Started. Waiting for Player(s)"); @@ -786,12 +694,15 @@ private async Task WaitForPlayersToSpawn() } //}); - ReadyToStartGamePacket packet = new ReadyToStartGamePacket(SITMatchmaking.Profile.ProfileId); + ReadyToStartGamePacket packet = new(SITMatchmaking.Profile.ProfileId); GameClient.SendData(packet.Serialize()); } private async Task WaitForPlayersToBeReady() { + if (SITMatchmaking.IsServer) + _ = SITGameModeHelpers.UpdateRaidStatusAsync(SITMPRaidStatus.WaitingToStart); + if (SITMatchmaking.TimeHasComeScreenController != null) { SITMatchmaking.TimeHasComeScreenController.ChangeStatus($"Players spawned. Waiting for Player(s) to be Ready."); @@ -814,7 +725,7 @@ private async Task WaitForPlayersToBeReady() { System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); // Start the stopwatch immediately. - + do { @@ -848,13 +759,16 @@ private async Task WaitForPlayersToBeReady() if (!SITMatchmaking.IsClient) { - HostStartingGamePacket packet = new HostStartingGamePacket(); + HostStartingGamePacket packet = new(); GameClient.SendData(packet.Serialize()); } } private async Task WaitForHostToStart() { + if (SITMatchmaking.IsServer) + _ = SITGameModeHelpers.UpdateRaidStatusAsync(SITMPRaidStatus.WaitingToStart); + if (SITMatchmaking.TimeHasComeScreenController != null) { SITMatchmaking.TimeHasComeScreenController.ChangeStatus($"Players spawned and ready. Waiting for Host to start."); @@ -904,7 +818,7 @@ private async Task WaitForHostToStart() private void SendRequestSpawnPlayersPacket() { - RequestSpawnPlayersPacket requestSpawnPlayersPacket = new RequestSpawnPlayersPacket([Singleton.Instance.MainPlayer.ProfileId]); + RequestSpawnPlayersPacket requestSpawnPlayersPacket = new([Singleton.Instance.MainPlayer.ProfileId]); GameClient.SendData(requestSpawnPlayersPacket.Serialize()); } @@ -957,19 +871,17 @@ public override IEnumerator vmethod_4(float startDelay, BotControllerSettings co Logger.LogDebug("Bot Spawner System has been turned off - You are running as Client"); } - if (!SITMatchmaking.IsClient) - { - - var nonwaves = (WaveInfo[])ReflectionHelpers.GetFieldFromTypeByFieldType(nonWavesSpawnScenario_0.GetType(), typeof(WaveInfo[])).GetValue(nonWavesSpawnScenario_0); - - BotsPresets profileCreator = + var nonwaves = (WaveInfo[])ReflectionHelpers.GetFieldFromTypeByFieldType(nonWavesSpawnScenario_0.GetType(), typeof(WaveInfo[])).GetValue(nonWavesSpawnScenario_0); + BotsPresets profileCreator = new(BackEndSession , wavesSpawnScenario_0.SpawnWaves , this.BossWaves , nonwaves , true); + BotCreator botCreator = new(this, profileCreator, CreatePhysicalBot); - BotCreator botCreator = new(this, profileCreator, CreatePhysicalBot); + if (!SITMatchmaking.IsClient) + { BotZone[] botZones = LocationScene.GetAllObjects(false).ToArray(); PBotsController.Init(this , botCreator @@ -1023,6 +935,10 @@ public override IEnumerator vmethod_4(float startDelay, BotControllerSettings co Logger.LogError(ex); } } + else + { + PBotsController.Init(this, botCreator, [], SpawnSystem, wavesSpawnScenario_0.BotLocationModifier, false, false, false, false, false, Singleton.Instance, ""); + } yield return new WaitForSeconds(startDelay); @@ -1076,7 +992,7 @@ public override IEnumerator vmethod_4(float startDelay, BotControllerSettings co try { bool isWinter = BackEndSession.IsWinter; - WinterEventController winterEventController = new WinterEventController(); + WinterEventController winterEventController = new(); ReflectionHelpers.GetFieldFromTypeByFieldType(typeof(GameWorld), typeof(WinterEventController)).SetValue(Singleton.Instance, winterEventController); winterEventController.Run(isWinter).ContinueWith(x => { if (x.IsFaulted) Logger.LogError(x.Exception); return Task.CompletedTask; }); } @@ -1093,7 +1009,7 @@ public override IEnumerator vmethod_4(float startDelay, BotControllerSettings co if (nonWavesSpawnScenario_0 != null) nonWavesSpawnScenario_0.Run(); - if(wavesSpawnScenario_0 != null) + if (wavesSpawnScenario_0 != null) wavesSpawnScenario_0.Run(); } @@ -1110,6 +1026,10 @@ public override IEnumerator vmethod_4(float startDelay, BotControllerSettings co // below is vmethod_5 CreateExfiltrationPointAndInitDeathHandler(); } + + if (SITMatchmaking.IsServer) + _ = SITGameModeHelpers.UpdateRaidStatusAsync(SITMPRaidStatus.InGame); + runCallback.Succeed(); } @@ -1201,9 +1121,6 @@ private void ExfiltrationPoint_OnCancelExtraction(ExfiltrationPoint point, EFT.P private void ExfiltrationPoint_OnStartExtraction(ExfiltrationPoint point, EFT.Player player) { - if (!player.IsYourPlayer) - return; - Logger.LogDebug($"{nameof(ExfiltrationPoint_OnStartExtraction)} {point.Settings.Name} {point.Status} {point.Settings.ExfiltrationTime}"); bool playerHasMetRequirements = !point.UnmetRequirements(player).Any(); if (!ExtractingPlayers.ContainsKey(player.ProfileId) && !ExtractedPlayers.Contains(player.ProfileId)) @@ -1212,8 +1129,12 @@ private void ExfiltrationPoint_OnStartExtraction(ExfiltrationPoint point, EFT.Pl Logger.LogDebug($"Added {player.ProfileId} to {nameof(ExtractingPlayers)}"); } - MyExitLocation = point.Settings.Name; - MyExitStatus = ExitStatus.Survived; + // Setup MyExitLocation and MyExitStatus only if this is My Player + if (player.IsYourPlayer) + { + MyExitLocation = point.Settings.Name; + MyExitStatus = ExitStatus.Survived; + } } private void ExfiltrationPoint_OnStatusChanged(ExfiltrationPoint point, EExfiltrationStatus prevStatus) @@ -1294,10 +1215,11 @@ public override void Stop(string profileId, ExitStatus exitStatus, string exitNa foreach (var p in SITGameComponent.GetCoopGameComponent().Players) { var pid = p.Value.ProfileId; + bool isBot = p.Value.IsAI; // make sure the host does not "leave game" before other players since Relay has special handling Aki-side - if (pid != hostProfileId) + if (pid != hostProfileId && !isBot) { - AkiBackendCommunication.Instance.PostJson("/coop/server/update", new Dictionary() { + AkiBackendCommunication.Instance.PostJsonBLOCKING("/coop/server/update", new Dictionary() { { "m", "PlayerLeft" }, { "profileId", pid }, { "serverId", SITGameComponent.GetServerId() } @@ -1307,13 +1229,16 @@ public override void Stop(string profileId, ExitStatus exitStatus, string exitNa } // Notify that I have left the Server - AkiBackendCommunication.Instance.PostJson("/coop/server/update", new Dictionary() { + AkiBackendCommunication.Instance.PostJsonBLOCKING("/coop/server/update", new Dictionary() { { "m", "PlayerLeft" }, { "profileId", Singleton.Instance.MainPlayer.ProfileId }, { "serverId", SITGameComponent.GetServerId() } }.ToJson()); + if (SITMatchmaking.IsServer) + _ = SITGameModeHelpers.UpdateRaidStatusAsync(SITMPRaidStatus.Complete); + if (BossWaveManager != null) BossWaveManager.Stop(); @@ -1383,41 +1308,32 @@ public override void Stop(string profileId, ExitStatus exitStatus, string exitNa public override void CleanUp() { - foreach (EFT.Player value in Bots.Values) - { - try - { - value.Dispose(); - AssetPoolObject.ReturnToPool(value.gameObject); - } - catch (Exception exception) - { - UnityEngine.Debug.LogException(exception); - } - } - Bots.Clear(); - if (SITGameComponent.TryGetCoopGameComponent(out var gameComponent)) { - if (gameComponent.PlayerClients != null) + if (gameComponent.Players != null) { - foreach (EFT.Player value in gameComponent.PlayerClients) + foreach (var value in gameComponent.Players.Values) { try { + if (value == null) + continue; + value.Dispose(); - AssetPoolObject.ReturnToPool(value.gameObject); + + if (value.gameObject != null) + AssetPoolObject.ReturnToPool(value.gameObject); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } } - gameComponent.PlayerClients.Clear(); } } - base.CleanUp(); + gameComponent.PlayerClients.Clear(); + Bots.Clear(); } public override void Dispose() @@ -1425,7 +1341,8 @@ public override void Dispose() Logger.LogDebug("CoopGame:Dispose()"); StartCoroutine(DisposingCo()); - foreach (ExfiltrationPoint exfiltrationPoint in ExfiltrationControllerClass.Instance.EligiblePoints(Profile_0)) { + foreach (ExfiltrationPoint exfiltrationPoint in ExfiltrationControllerClass.Instance.EligiblePoints(Profile_0)) + { exfiltrationPoint.OnStartExtraction -= ExfiltrationPoint_OnStartExtraction; exfiltrationPoint.OnCancelExtraction -= ExfiltrationPoint_OnCancelExtraction; exfiltrationPoint.OnStatusChanged -= ExfiltrationPoint_OnStatusChanged; @@ -1449,6 +1366,7 @@ private IEnumerator DisposingCo() public BossLocationSpawn[] BossWaves { get; private set; } public int ReadyPlayers { get; set; } public bool HostReady { get; set; } + public bool GameWorldStarted { get; set; } private NonWavesSpawnScenario nonWavesSpawnScenario_0; @@ -1465,6 +1383,7 @@ public async Task Run(BotControllerSettings botsSettings, string backendUrl, Inv { Logger.LogDebug(nameof(Run)); + Singleton.Instance.AfterGameStarted += Instance_AfterGameStarted; base.Status = GameStatus.Running; UnityEngine.Random.InitState((int)DateTime.UtcNow.Ticks); LocationSettingsClass.Location location; @@ -1481,33 +1400,19 @@ public async Task Run(BotControllerSettings botsSettings, string backendUrl, Inv location = await BackEndSession.LoadLocationLoot(Location_0.Id, variantId); } } - BackendConfigSettingsClass instance = Singleton.Instance; - if (instance != null && instance.HalloweenSettings.EventActive && !instance.HalloweenSettings.LocationsToIgnore.Contains(location._Id)) - { - GameObject gameObject = (GameObject)Resources.Load("Prefabs/HALLOWEEN_CONTROLLER"); - if (gameObject != null) - { - GClass5.InstantiatePrefab(base.transform, gameObject); - } - else - { - UnityEngine.Debug.LogError("Can't find event prefab in resources. Path : Prefabs/HALLOWEEN_CONTROLLER"); - } - } + BackendConfigManagerConfig config = BackendConfigManager.Config; if (config.FixedFrameRate > 0f) { base.FixedDeltaTime = 1f / config.FixedFrameRate; } - //using (TokenStarter.StartWithToken("player create")) - { - EFT.Player player = await CreatePlayerSpawn(); - dictionary_0.Add(player.ProfileId, player); - gparam_0 = func_1(player); - PlayerCameraController.Create(gparam_0.Player); - FPSCamera.Instance.SetOcclusionCullingEnabled(Location_0.OcculsionCullingEnabled); - FPSCamera.Instance.IsActive = false; - } + + EFT.Player player = await CreatePlayerSpawn(); + dictionary_0.Add(player.ProfileId, player); + gparam_0 = func_1(player); + PlayerCameraController.Create(gparam_0.Player); + CameraClass.Instance.SetOcclusionCullingEnabled(Location_0.OcculsionCullingEnabled); + CameraClass.Instance.IsActive = false; await SpawnLoot(location); await WaitForPlayersToSpawn(); @@ -1517,6 +1422,11 @@ public async Task Run(BotControllerSettings botsSettings, string backendUrl, Inv method_5(botsSettings, SpawnSystem, runCallback); } + private void Instance_AfterGameStarted() + { + GameWorldStarted = true; + } + class PlayerLoopSystemType { public PlayerLoopSystemType() @@ -1524,7 +1434,7 @@ public PlayerLoopSystemType() } } - + public async Task SpawnLoot(LocationSettingsClass.Location location) { @@ -1532,7 +1442,7 @@ public async Task SpawnLoot(LocationSettingsClass.Location location) using (TokenStarter.StartWithToken("SpawnLoot")) { - Item[] source = location.Loot.Select((GLootItem x) => x.Item).ToArray(); + Item[] source = location.Loot.Select((LootItemPositionClass x) => x.Item).ToArray(); ResourceKey[] array = source.OfType().GetAllItemsFromCollections().Concat(source.Where((Item x) => !(x is ContainerCollection))).SelectMany((Item x) => x.Template.AllResources) .ToArray(); if (array.Length != 0) @@ -1542,10 +1452,7 @@ public async Task SpawnLoot(LocationSettingsClass.Location location) PlayerLoopSystem playerLoopSystem = default(PlayerLoopSystem); var index = 0; - //ReflectionHelpers.GetMethodForType(parentPlayerLoopSystemType, "FindParentPlayerLoopSystem").Invoke(null, new object[] { currentPlayerLoop, typeof(EarlyUpdate.UpdateTextureStreamingManager), playerLoopSystem, index }); - - // TODO: Remap or figure out a way to avoid this - GClass572.FindParentPlayerLoopSystem(currentPlayerLoop, typeof(EarlyUpdate.UpdateTextureStreamingManager), out playerLoopSystem, out index); + PlayerLoopSystemHelpers.FindParentPlayerLoopSystem(currentPlayerLoop, typeof(EarlyUpdate.UpdateTextureStreamingManager), out playerLoopSystem, out index); PlayerLoopSystem[] array2 = new PlayerLoopSystem[playerLoopSystem.subSystemList.Length]; if (index != -1) { @@ -1557,7 +1464,7 @@ public async Task SpawnLoot(LocationSettingsClass.Location location) playerLoopSystem.subSystemList[index] = playerLoopSystem3; PlayerLoop.SetPlayerLoop(currentPlayerLoop); } - await Singleton.Instance.LoadBundlesAndCreatePools(PoolManager.PoolsCategory.Raid, PoolManager.AssemblyType.Local, array, JobPriority.General, new GClass3273(delegate (GStruct118 p) + await Singleton.Instance.LoadBundlesAndCreatePools(PoolManager.PoolsCategory.Raid, PoolManager.AssemblyType.Local, array, JobPriority.General, new BundleLoaderProgress(p => { SetMatchmakerStatus("Loading loot... " + p.Stage, p.Progress); })); @@ -1579,10 +1486,22 @@ public async Task SpawnLoot(LocationSettingsClass.Location location) int playerId = 1; EFT.Player.EUpdateMode armsUpdateMode = EFT.Player.EUpdateMode.Auto; - LocalPlayer obj = await vmethod_2(playerId, Vector3.zero, Quaternion.identity, "Player", "", EPointOfView.FirstPerson, Profile_0, aiControl: false, base.UpdateQueue, armsUpdateMode, EFT.Player.EUpdateMode.Auto, BackendConfigManager.Config.CharacterController.ClientPlayerMode, () => Singleton.Instance.Control.Settings.MouseSensitivity, () => Singleton.Instance.Control.Settings.MouseAimingSensitivity, new GAbstractStatisticsManager(), null, null); + LocalPlayer obj = await vmethod_2(playerId, Vector3.zero, Quaternion.identity, "Player", "", EPointOfView.FirstPerson, Profile_0, aiControl: false, base.UpdateQueue, armsUpdateMode, EFT.Player.EUpdateMode.Auto, BackendConfigManager.Config.CharacterController.ClientPlayerMode, () => Singleton.Instance.Control.Settings.MouseSensitivity, () => Singleton.Instance.Control.Settings.MouseAimingSensitivity, null, null, null); obj.Location = Location_0.Id; obj.OnEpInteraction += base.OnEpInteraction; return obj; } + + public bool Ready() + { + if (GameServer != null) + { + return GameServer._netServer?.IsRunning ?? false; + } + else + { + return true; + } + } } } diff --git a/Source/Coop/SITGameModes/ISITGame.cs b/Source/Coop/SITGameModes/ISITGame.cs index 2f416d676..fad03770d 100644 --- a/Source/Coop/SITGameModes/ISITGame.cs +++ b/Source/Coop/SITGameModes/ISITGame.cs @@ -43,9 +43,13 @@ public interface ISITGame public Task Run(BotControllerSettings botsSettings, string backendUrl, InventoryControllerClass inventoryController, Callback runCallback); - + public bool GameWorldStarted { get; set; } //Task WaitForPlayersToSpawn(); //Task WaitForPlayersToBeReady(); + GameDateTime GameDateTime { get; set; } + + public GameTimerClass GameTimer { get; set; } + } } diff --git a/Source/Coop/SITGameModes/MultiplayerSITGame.cs b/Source/Coop/SITGameModes/MultiplayerSITGame.cs index 6eb3332fb..1aa68ef1a 100644 --- a/Source/Coop/SITGameModes/MultiplayerSITGame.cs +++ b/Source/Coop/SITGameModes/MultiplayerSITGame.cs @@ -10,6 +10,7 @@ using EFT.Weather; using StayInTarkov.Networking; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -40,10 +41,12 @@ public sealed class MultiplayerSITGame : BaseLocalGame, IBotGam public int ReadyPlayers { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public bool HostReady { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public bool GameWorldStarted { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public Task Run(BotControllerSettings botsSettings, string backendUrl, InventoryControllerClass inventoryController, Callback runCallback) { throw new NotImplementedException(); } + } } diff --git a/Source/Coop/SITGameModes/SITGameModeHelpers.cs b/Source/Coop/SITGameModes/SITGameModeHelpers.cs index 68396fdf7..73f644faa 100644 --- a/Source/Coop/SITGameModes/SITGameModeHelpers.cs +++ b/Source/Coop/SITGameModes/SITGameModeHelpers.cs @@ -1,6 +1,10 @@ -using System; +using Newtonsoft.Json.Linq; +using StayInTarkov.Coop.Components.CoopGameComponents; +using StayInTarkov.Networking; +using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -8,6 +12,12 @@ namespace StayInTarkov.Coop.SITGameModes { public static class SITGameModeHelpers { - + public static async Task UpdateRaidStatusAsync(SITMPRaidStatus status) + { + JObject jobj = new JObject(); + jobj.Add("status", status.ToString()); + jobj.Add("serverId", SITGameComponent.GetServerId()); + await AkiBackendCommunication.Instance.PostJsonAsync("/coop/server/update", jobj.ToString()); + } } } diff --git a/Source/Coop/SITGameModes/SITMPRaidStatus.cs b/Source/Coop/SITGameModes/SITMPRaidStatus.cs new file mode 100644 index 000000000..23a8dd3dd --- /dev/null +++ b/Source/Coop/SITGameModes/SITMPRaidStatus.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StayInTarkov.Coop.SITGameModes +{ + public enum SITMPRaidStatus + { + Loading, + WaitingForPlayers, + WaitingToStart, + InGame, + Complete + } +} diff --git a/Source/Coop/Session/LoadLocationLoot_Patch.cs b/Source/Coop/Session/LoadLocationLoot_Patch.cs index 5ad3d3530..10ca465df 100644 --- a/Source/Coop/Session/LoadLocationLoot_Patch.cs +++ b/Source/Coop/Session/LoadLocationLoot_Patch.cs @@ -1,17 +1,14 @@ -using Aki.Custom.Airdrops.Models; -using EFT; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json; using StayInTarkov.Coop.Matchmaker; +using StayInTarkov.Coop.NetworkPacket.Raid; using StayInTarkov.Networking; -using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; +using System.Security.Cryptography; using System.Threading.Tasks; -using UnityEngine.Networking.Match; -using UnityEngine.Profiling; +using UnityEngine.UIElements.UIR.Implementation; +using UnityEngine.VR; +using UnityEngineInternal.XR.WSA; namespace StayInTarkov.Coop.Session { @@ -25,7 +22,7 @@ public class LoadLocationLootPatch : ModulePatch { protected override MethodBase GetTargetMethod() { - var method = ReflectionHelpers.GetMethodForType(typeof(TradingBackend1), "LoadLocationLoot"); + var method = ReflectionHelpers.GetMethodForType(typeof(TradingBackend), "LoadLocationLoot"); Logger.LogDebug($"{GetType().Name} Method: {method?.Name}"); @@ -51,20 +48,20 @@ private static bool PatchPrefix(string locationId, int variantId, ref Task + var rsp = AkiBackendCommunication.Instance.PostJsonBLOCKING($"/coop/location/getLoot", JsonConvert.SerializeObject(objectToSend)); + if (rsp == null) { - var result = AkiBackendCommunication.Instance.PostJson($"/coop/location/getLoot", JsonConvert.SerializeObject(objectToSend)); - if (result != null) - { - result.TrySITParseJson(out LocationDataRequest locationDataRequest); - if (locationDataRequest != null) - return locationDataRequest.Data; - } - return null; - }); + return true; + } + + rsp.TrySITParseJson(out LocationDataRequest locationDataRequest); + if (locationDataRequest == null) + { + return true; + } - // Protection. If the __result is null, then run the original method - return __result == null; + __result = Task.FromResult(locationDataRequest.Data); + return false; } } } diff --git a/Source/Coop/TarkovApplication_LocalGameCreator_Patch.cs b/Source/Coop/TarkovApplication_LocalGameCreator_Patch.cs index d834957fd..2a41723f0 100644 --- a/Source/Coop/TarkovApplication_LocalGameCreator_Patch.cs +++ b/Source/Coop/TarkovApplication_LocalGameCreator_Patch.cs @@ -25,7 +25,7 @@ protected override MethodBase GetTargetMethod() x.GetParameters().Length >= 2 && x.GetParameters()[0].ParameterType == typeof(TimeAndWeatherSettings) - && x.GetParameters()[1].ParameterType == typeof(MatchmakerTimeHasCome.TimeHasComeScreenController) + && x.GetParameters()[1].ParameterType == typeof(TimeHasComeScreenController) ); } @@ -56,7 +56,7 @@ public static async Task Postfix( Task __result, TarkovApplication __instance, TimeAndWeatherSettings timeAndWeather, - MatchmakerTimeHasCome.TimeHasComeScreenController timeHasComeScreenController, + TimeHasComeScreenController timeHasComeScreenController, RaidSettings ____raidSettings, InputTree ____inputTree, GameDateTime ____localGameDateTime, @@ -137,7 +137,7 @@ string ____backendUrl , ____raidSettings.SelectedDateTime , new Callback((r) => { - // target private async void method_46(string profileId, Profile savageProfile, LocationSettingsClass.Location location, Result result, MatchmakerTimeHasCome.TimeHasComeScreenController timeHasComeScreenController = null) + // target private async void method_46(string profileId, Profile savageProfile, LocationSettingsClass.Location location, Result result, TimeHasComeScreenController timeHasComeScreenController = null) //Logger.LogInfo("Callback Metrics. Invoke method 45"); //ReflectionHelpers.GetMethodForType(__instance.GetType(), "method_45").Invoke(__instance, new object[] { //session.Profile.Id, session.ProfileOfPet, ____raidSettings.SelectedLocation, r, timeHasComeScreenController @@ -150,7 +150,7 @@ string ____backendUrl && x.GetParameters()[1].ParameterType == typeof(Profile) && x.GetParameters()[2].ParameterType == typeof(LocationSettingsClass.Location) && x.GetParameters()[3].ParameterType == typeof(Result) - && x.GetParameters()[4].ParameterType == typeof(MatchmakerTimeHasCome.TimeHasComeScreenController) + && x.GetParameters()[4].ParameterType == typeof(TimeHasComeScreenController) ).Invoke(__instance, new object[] { session.Profile.Id, session.ProfileOfPet, ____raidSettings.SelectedLocation, r, timeHasComeScreenController }); @@ -161,6 +161,11 @@ string ____backendUrl , TimeSpan.FromSeconds(60 * ____raidSettings.SelectedLocation.EscapeTimeLimit) //} ); + var i = 100; + while (!localGame.Ready() && i-- > 0) + { + await Task.Delay(100); + } Singleton.Create(localGame); timeHasComeScreenController.ChangeStatus(StayInTarkovPlugin.LanguageDictionary["CREATED_COOP_GAME"].ToString()); diff --git a/Source/Coop/World/LootableContainer_Interact_Patch.cs b/Source/Coop/World/LootableContainer_Interact_Patch.cs index f3cf46f06..280819dc3 100644 --- a/Source/Coop/World/LootableContainer_Interact_Patch.cs +++ b/Source/Coop/World/LootableContainer_Interact_Patch.cs @@ -1,6 +1,7 @@ using EFT; using EFT.Interactive; using StayInTarkov.Coop.Components.CoopGameComponents; +using StayInTarkov.Coop.NetworkPacket.Raid; using StayInTarkov.Networking; using System; using System.Collections.Concurrent; @@ -45,65 +46,24 @@ public static bool Prefix(LootableContainer __instance) [PatchPostfix] public static void Postfix(LootableContainer __instance, InteractionResult interactionResult) { - Dictionary packet = new() - { - { "t", DateTime.Now.Ticks.ToString("G") }, - { "serverId", SITGameComponent.GetServerId() }, - { "m", MethodName }, - { "lootableContainerId", __instance.Id }, - { "type", interactionResult.InteractionType.ToString() } - }; + //Dictionary packet = new() + //{ + // { "t", DateTime.Now.Ticks.ToString("G") }, + // { "serverId", SITGameComponent.GetServerId() }, + // { "m", MethodName }, + // { "lootableContainerId", __instance.Id }, + // { "type", interactionResult.InteractionType.ToString() } + //}; + + LootableContainerInteractionPacket packet = new(); + packet.LootableContainerId = __instance.Id; + packet.InteractionType = interactionResult.InteractionType; - GameClient.SendData(packet.ToJson()); + GameClient.SendData(packet.Serialize()); } public static void Replicated(Dictionary packet) { - if (HasProcessed(packet)) - return; - - if (Enum.TryParse(packet["type"].ToString(), out EInteractionType interactionType)) - { - SITGameComponent coopGameComponent = SITGameComponent.GetCoopGameComponent(); - LootableContainer lootableContainer = coopGameComponent.ListOfInteractiveObjects.FirstOrDefault(x => x.Id == packet["lootableContainerId"].ToString()) as LootableContainer; - - if (lootableContainer != null) - { - string methodName = string.Empty; - switch (interactionType) - { - case EInteractionType.Open: - methodName = "Open"; - break; - case EInteractionType.Close: - methodName = "Close"; - break; - case EInteractionType.Unlock: - methodName = "Unlock"; - break; - case EInteractionType.Breach: - break; - case EInteractionType.Lock: - methodName = "Lock"; - break; - } - - void Interact() => ReflectionHelpers.InvokeMethodForObject(lootableContainer, methodName); - - if (interactionType == EInteractionType.Unlock) - Interact(); - else - lootableContainer.StartBehaviourTimer(EFTHardSettings.Instance.DelayToOpenContainer, Interact); - } - else - { - Logger.LogDebug("LootableContainer_Interact_Patch:Replicated: Couldn't find LootableContainer in at all in world?"); - } - } - else - { - Logger.LogError("LootableContainer_Interact_Patch:Replicated:EInteractionType did not parse correctly!"); - } } } } \ No newline at end of file diff --git a/Source/EssentialPatches/Bundles/BundleManager.cs b/Source/EssentialPatches/Bundles/BundleManager.cs index 42d76048e..e2a25cc96 100644 --- a/Source/EssentialPatches/Bundles/BundleManager.cs +++ b/Source/EssentialPatches/Bundles/BundleManager.cs @@ -1,5 +1,6 @@ using Aki.Custom.Models; using BepInEx.Logging; +using EFT.UI; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using StayInTarkov.EssentialPatches; @@ -9,6 +10,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -39,10 +41,10 @@ public static string GetBundlePath(BundleItem bundle) : CachePath + bundle.FileName; } - public static void GetBundles() + public static async Task GetBundles() { // get bundles - var json = AkiBackendCommunication.Instance.GetJson("/singleplayer/bundles"); + var json = await AkiBackendCommunication.Instance.GetJsonAsync("/singleplayer/bundles"); var bundles = JsonConvert.DeserializeObject(json); StayInTarkovHelperConstants.Logger.LogDebug($"[Bundle Manager] Bundles Json: {json}"); @@ -71,6 +73,7 @@ public static void GetBundles() } else { + // FIXME(belette) implement bounded parallelism // download bundles // NOTE: assumes bundle keys to be unique foreach (var bundle in toDownload) @@ -81,7 +84,7 @@ public static void GetBundles() try { // Using GetBundleData to download Bundle because the timeout period is 5 minutes.(For big bundles) - var data = AkiBackendCommunication.Instance.GetBundleData($"/files/bundle/{bundle.FileName}"); + var data = await AkiBackendCommunication.Instance.GetBundleData($"/files/bundle/{bundle.FileName}"); if (data != null && data.Length == 0) { StayInTarkovHelperConstants.Logger.LogError("Bundle received is 0 bytes. WTF!"); @@ -104,7 +107,7 @@ public static void GetBundles() try { // Using GetBundleData to download Bundle because the timeout period is 10 minutes.(For big bundles) - var data = AkiBackendCommunication.Instance.GetBundleData($"/files/bundle/{bundle.FileName}", 600000); + var data = await AkiBackendCommunication.Instance.GetBundleData($"/files/bundle/{bundle.FileName}", 600000); if (data != null && data.Length == 0) { StayInTarkovHelperConstants.Logger.LogError("Bundle received is 0 bytes. WTF!"); diff --git a/Source/EssentialPatches/CoreUtilities/ModulePatch.cs b/Source/EssentialPatches/CoreUtilities/ModulePatch.cs index 6e5c81d6b..afac1aa19 100644 --- a/Source/EssentialPatches/CoreUtilities/ModulePatch.cs +++ b/Source/EssentialPatches/CoreUtilities/ModulePatch.cs @@ -1,6 +1,7 @@ using BepInEx.Logging; using EFT.UI; using HarmonyLib; +using MonoMod.RuntimeDetour; using System; using System.Collections.Generic; using System.Linq; diff --git a/Source/EssentialPatches/VersionLabelPatch.cs b/Source/EssentialPatches/VersionLabelPatch.cs index fc4378d67..ce2a64896 100644 --- a/Source/EssentialPatches/VersionLabelPatch.cs +++ b/Source/EssentialPatches/VersionLabelPatch.cs @@ -1,4 +1,5 @@ -using BepInEx.Configuration; +using BepInEx.Bootstrap; +using BepInEx.Configuration; using EFT.UI; using HarmonyLib; using System; @@ -16,12 +17,14 @@ namespace StayInTarkov.EssentialPatches /// public class VersionLabelPatch : ModulePatch { - private static string _versionLabel; + //private static string _versionLabel; private static bool EnableSITVersionLabel { get; set; } = true; + private static bool IsDevBuild => true; + public VersionLabelPatch(ConfigFile config) { - EnableSITVersionLabel = config.Bind("SIT.SP", "EnableSITVersionLabel", true).Value; + EnableSITVersionLabel = config.Bind("SIT", "EnableSITVersionLabel", true).Value; } protected override MethodBase GetTargetMethod() @@ -50,14 +53,15 @@ internal static void PatchPostfix( //GetLogger(typeof(VersionLabelPatch)).LogInfo("Postfix"); } - private static void DisplaySITVersionLabel(string major, object __result) + public static void DisplaySITVersionLabel(string major, object __result) { if (!EnableSITVersionLabel) return; + string _versionLabel = string.Empty; - if (string.IsNullOrEmpty(_versionLabel)) + //if (string.IsNullOrEmpty(_versionLabel)) { - _versionLabel = string.Empty; + //_versionLabel = string.Empty; var eftPath = string.Empty; var eftProcesses = Process.GetProcessesByName("EscapeFromTarkov"); foreach (var process in eftProcesses) @@ -77,6 +81,7 @@ private static void DisplaySITVersionLabel(string major, object __result) StayInTarkovPlugin.EFTEXEFileVersion = myFileVersionInfo.ProductVersion.Split('-')[0] + "." + myFileVersionInfo.ProductVersion.Split('-')[1]; } } + string sitversion = Assembly.GetAssembly(typeof(VersionLabelPatch)).GetName().Version.ToString(); StayInTarkovPlugin.EFTVersionMajor = major; StayInTarkovPlugin.EFTAssemblyVersion = major; @@ -86,12 +91,20 @@ private static void DisplaySITVersionLabel(string major, object __result) Logger.LogInfo($"Assembly {StayInTarkovPlugin.EFTAssemblyVersion} does not match {StayInTarkovPlugin.EFTEXEFileVersion}"); } else + { _versionLabel = $"SIT {sitversion} | {StayInTarkovPlugin.EFTAssemblyVersion}"; +#if DEBUG + _versionLabel = $"SIT {sitversion} | {StayInTarkovPlugin.EFTAssemblyVersion} {(IsDevBuild ? "[DEV]" : "")} [{Chainloader.Plugins.Count-1} mods]"; +#endif + + } + } Traverse.Create(MonoBehaviourSingleton.Instance).Field("_alphaVersionLabel").Property("LocalizationKey").SetValue("{0}"); Traverse.Create(MonoBehaviourSingleton.Instance).Field("string_2").SetValue(_versionLabel); - Traverse.Create(__result).Field("Major").SetValue(_versionLabel); + if(__result != null) + Traverse.Create(__result).Field("Major").SetValue(_versionLabel); } } } \ No newline at end of file diff --git a/Source/EssentialPatches/Web/SendCommandsPatch.cs b/Source/EssentialPatches/Web/SendCommandsPatch.cs index 75e7d259a..d5c48605f 100644 --- a/Source/EssentialPatches/Web/SendCommandsPatch.cs +++ b/Source/EssentialPatches/Web/SendCommandsPatch.cs @@ -10,7 +10,7 @@ internal class SendCommandsPatch : ModulePatch { protected override MethodBase GetTargetMethod() { - return ReflectionHelpers.GetMethodForType(typeof(TradingBackend1), "TrySendCommands"); + return ReflectionHelpers.GetMethodForType(typeof(TradingBackend), "TrySendCommands"); } [PatchPrefix] diff --git a/Source/EssentialPatches/Web/WebSocketPatch.cs b/Source/EssentialPatches/Web/WebSocketPatch.cs index 6dc619db7..8a6cb0d01 100644 --- a/Source/EssentialPatches/Web/WebSocketPatch.cs +++ b/Source/EssentialPatches/Web/WebSocketPatch.cs @@ -6,6 +6,8 @@ namespace StayInTarkov { public class WebSocketPatch : ModulePatch { + public static bool IsHttps = true; + protected override MethodBase GetTargetMethod() { var targetInterface = StayInTarkovHelperConstants.EftTypes.SingleCustom(x => x == typeof(IConnectionHandler) && x.IsInterface); @@ -19,7 +21,16 @@ protected override MethodBase GetTargetMethod() [PatchPostfix] private static Uri PatchPostfix(Uri __result) { - return new Uri(__result.ToString().Replace("wss:", "ws:")); + UriBuilder websocketUriBuilder = new UriBuilder(__result); + string uriString = websocketUriBuilder.Uri.ToString(); + if (uriString.StartsWith((IsHttps) ? "wss" : "ws")) + { + UriBuilder backendUriBuilder = new UriBuilder(StayInTarkovHelperConstants.GetBackendUrl()); + websocketUriBuilder.Host = backendUriBuilder.Host; + websocketUriBuilder.Port = backendUriBuilder.Port; + if((uriString.StartsWith("wss") && !IsHttps) || (uriString.StartsWith("ws") && IsHttps)) websocketUriBuilder = new UriBuilder(uriString.Replace((IsHttps) ? "ws" : "wss", (IsHttps) ? "wss" : "ws")); + } + return websocketUriBuilder.Uri; } } diff --git a/Source/GlobalUsings.cs b/Source/GlobalUsings.cs new file mode 100644 index 000000000..fd4a6c5c4 --- /dev/null +++ b/Source/GlobalUsings.cs @@ -0,0 +1,1809 @@ +global using IReader = GInterface76; +global using IWriter = GInterface79; +global using Quest = GClass1249; +global using IOperation1 = GInterface338; +global using EventsPriority = GClass578; +global using Node1 = GClass2888; +global using AmmoPack = GClass2495; +global using Peer = GClass1282; +global using Preset1 = GClass2092; +global using Data1 = GClass591; +global using GPlayer = GClass1210; +global using Execute = GStruct413; +global using Note = GClass2493; +global using IGIPlayer = GInterface94; +global using Packet = Class826; +global using IContext = GInterface222; +global using BuffSettings = GClass2424.GClass2449.GClass2450; +global using IStart = GInterface223; +global using Model1 = GStruct277; +global using GKeyCombination = GClass1895; +global using Template1 = GClass3256; +global using ISerializer = GClass961.GInterface50; +global using Customization = GClass1755; +global using AbstractScheme = GClass1922; +global using GridItemAddress = GClass2769; +global using AbstractInventoryOperation = GClass2837; +global using IBuff = GInterface228; +global using Requisite = GClass2048; +global using AbstractEffect = EFT.HealthSystem.ActiveHealthController.GClass2415; +global using Filter1 = GClass3193; +global using GMessage1 = GClass2484; +global using RepairKit = GClass2730; +global using TimeHasComeScreenController = EFT.UI.Matchmaker.MatchmakerTimeHasCome.GClass3163; +global using Threshold = GClass1120; +global using SquadPlayer = GClass1209; +global using Context1 = GClass2820; +global using Size1 = GStruct24; +global using TarkovRequest = Class267; +global using Invite = GClass1213; +global using AbstractBonus = GClass1407; +global using AbstractProducer = GClass1915; +global using Scheme = GClass1923; +global using Show = GClass3087; +global using Quests = GClass3362; +global using Conditions = GClass3368; +global using Settings1 = GStruct236; +global using AbstractSkill = GClass1766; +global using Frustum = GClass1873; +global using TargetScenario = GClass1939; +global using IInputTree1 = GInterface157; +global using SubItem = GClass804.Class469; +global using ItemTemplates = GClass1206; +global using Config1 = GClass1800; +global using ProfileData = GClass1819.GClass1820; +global using GOffer = EFT.UI.ClothingItem.GClass3071; +global using Nodes1 = GClass3357; +global using ServiceData1 = GClass1794; +global using AbstractLine = GClass2056; +global using Connection1 = GClass2472; +global using AsyncData = GClass649.GClass653.GClass658; +global using Physical = GClass681; +global using CompositeDisposable = GClass766; +global using IconRequest = GClass824; +global using Invitation = GClass944; +global using IModel = GInterface213; +global using RestoreInfo = EFT.Profile.GClass1756.GClass1757; +global using AbstractBuff = EFT.SkillManager.GClass1773; +global using PresetItem = GClass2092.GClass2093; +global using InventoryController = GClass2764; +global using EquipmentBuild = GClass3182; +global using Build1 = GClass3184; +global using WorldTypeMask = GClass649.GClass653.EWorldType; +global using Data2 = GStruct133; +global using GStruct346Data = GStruct346.GStruct357; +global using Cache1 = Class265; +global using Position1 = ClassVector3; +global using GClass14PauseReason = GClass14.EPauseReason; +global using AxisCombination = GClass1894; +global using Improvement = GClass1911; +global using ProducingItem = GClass1921; +global using ArmorSlot = GClass2511; +global using UnderbarrelWeapon = GClass2668; +global using AbstractPassedEvent = GClass2889; +global using RagfairSearch = GClass3196; +global using ReqToSend = GClass564; +global using IOneItemOperation = GInterface339; +global using GStruct247IconType = GStruct247.EDialogLiteIconType; +global using Input1 = GStruct280; +global using RuntimeData = GClass1064; +global using AbstractState = GClass1108; +global using AbstractStateMachine = GClass1111; +global using ApplyShot = GClass1676; +global using GLine = GClass1824.GClass1825; +global using AddAmmoInChamber = GClass2390; +global using Operation1 = GClass2869; +global using ParamData = GClass2934.Struct775; +global using ConditionProgressChecker = GClass3232; +global using Offers = GClass3364; +global using Gluhar = GClass367; +global using ContainerCollectionView = GClass674; +global using UnparsedData = GClass751; +global using IQueueItem = GClass839.GInterface37; +global using PreDrawCallback = GClass849.GDelegate25; +global using IGIInventoryController = GInterface313; +global using IWeapon = GInterface322; +global using IOperationResult = GInterface323; +global using Data3 = GStruct132; +global using Movement = EFT.SkillManager.GStruct228; +global using SaveData = GStruct247.GStruct248; +global using GMessage2 = GStruct276; +global using ShotId = GStruct390; +global using Result1 = GStruct75; +global using AdapterId = EFT.Settings.Graphics.EftDisplay.Class1534.Struct453; +global using TarkovRequestTransportWS = Class276; +global using Column = Class3195; +global using PrototypeRenderer = SpeedTreeTerrainProcessor.Class584; +global using State1 = Class609.EState; +global using GAbstractState = GClass1115; +global using FlatItem = GClass1189; +global using LootItems = GClass1202; +global using BoneType1 = GClass1265.BoneType; +global using BoneSide1 = GClass1265.BoneSide; +global using Setting1 = GClass1940.Setting; +global using GInventoryController = GClass2763; +global using ResizeResult = GClass2787; +global using Clothing = GClass2937; +global using Point1 = GClass311; +global using Binds = GClass3176; +global using Body1 = GClass320; +global using Reward = GClass3245; +global using Closest = GClass346; +global using SectantPriest = GClass377; +global using GetVisibilityChecker = GClass738; +global using ITrading = GInterface180; +global using IAnimationController = GInterface182; +global using ITemplate1 = GInterface293; +global using Footprint = GStruct202; +global using Data4 = GStruct219; +global using OldData = GStruct240; +global using CompassPacket = GStruct314; +global using EnableInventoryPacket = GStruct319; +global using UsableItemPacket = GStruct341; +global using GetStatistics = GStruct366; +global using Rect1 = GStruct371; +global using GetInteractionParameters = EFT.Interactive.WorldInteractiveObject.GStruct386; +global using Idling = EFT.Player.GrenadeController.Class1025.EThrowState; +global using InventoryOperation = EFT.Player.Class1060; +global using GClass1141ConditionMode = GClass1141.EConditionModes; +global using Request1 = GClass1275; +global using NetManager = GClass1281; +global using DataWriter = GClass1289; +global using GClass1303FramerateLimit = GClass1303.GClass1305; +global using GBuffSettings = BackendConfigSettingsClass.GClass1345; +global using Restriction = BackendConfigSettingsClass.GClass1369; +global using Data5 = GClass627.GClass1469; +global using AbstractDescriptor = GClass1525; +global using AbstractDescriptor1 = GClass1532; +global using UtilityType = EFT.Player.FirearmController.GClass1609.EUtilityType; +global using WeaponEffectsManager = GClass1668; +global using Config2 = GClass1738.Config; +global using Settings3 = GClass1748; +global using ProfileHealth = EFT.Profile.GClass1756; +global using SceneToken = GClass1805.GClass1806; +global using GLine1 = GClass2055.GStruct249; +global using GStruct178OperationType = GStruct178.EOperationType; +global using AbstractCreate = GClass2417.GClass2420; +global using Probability = GClass2424.GClass2454; +global using ArmorPlate = GClass2633; +global using FindCultistAmulet = GClass2716; +global using Container1 = GClass2763.GClass2812; +global using DiscardResult = GClass2783; +global using RemoveResult = GClass2785; +global using ISelectionContext = GClass2820.GInterface337; +global using GItemContext = GClass2821; +global using InvokedEvent = GClass2893; +global using GClass2902InteractState = GClass2902.EInteractState; +global using SetupInfo = GClass2977; +global using PrimaryManifest = GClass2991; +global using Segment1 = GClass306; +global using GShow = GClass3085; +global using BuildsStorage = GClass3183; +global using BotObserveData = GClass424; +global using IMakeTimer = GClass553.GInterface13; +global using AbstractCreateLogConfigurator = GClass638; +global using PhysicalStamina = GClass681.GStruct35; +global using Queue1 = GClass790; +global using SonicInfo = SonicBulletSoundPlayer.GClass797; +global using Random1 = GClass893; +global using Ammo = GClass920.GStruct70; +global using Settings4 = GClass958; +global using ITarget = GInterface178; +global using IStateStorage = GInterface181; +global using IPopNewAmmoResult = GInterface324; +global using IRandoms = GInterface356; +global using IEmitter = GInterface38; +global using ICreateSampleSet = GInterface60; +global using IFilterCustomization = GInterface99; +global using FloatQuantizer = GStruct100; +global using GItem4 = GStruct104; +global using PreviousPacket = GStruct125; +global using GPreviousPacket = GStruct126; +global using GPacket = GStruct127; +global using Preset2 = GStruct230; +global using GLine2 = GStruct232; +global using GrenadePacket = GStruct323; +global using KnifePacket = GStruct329; +global using GStruct346Data1 = GStruct346.GStruct347; +global using GridId = GStruct367; +global using Pools1 = PoolManager.Class1185; +global using Piece = EFT.Interactive.WindowBreaker.Class2249; +global using PresetWrapper = EFT.UI.Builds.MagPresetsListView.Class2416; +global using WindowData = EFT.UI.ItemUiContext.Class2500; +global using DesiredSize = Class627; +global using ConnRequest = Class827; +global using Response1 = EFT.AbstractGameSession.Class879; +global using GTarget = ClassQuaternion; +global using GTarget1 = ClassTransformSync; +global using LayerStateMachine = GClass1112; +global using TriggeredTransition = GClass1114; +global using AbstractClipInfo = GClass1122; +global using FrameMeasurer = GClass1153; +global using Config3 = GClass1153.GClass620.GClass1154; +global using Preset3 = GClass1203; +global using GProfile = GClass1211; +global using Data6 = GClass1287; +global using Type1 = GClass1291.CallType; +global using GPacket1 = GClass1292; +global using MasteringGroup = BackendConfigSettingsClass.GClass1366; +global using OverheatSettings = BackendConfigSettingsClass.GClass1371; +global using Slots = GClass1431; +global using Registry1 = GClass1487; +global using InventoryDescriptor = GClass1489; +global using FastAccessDescriptor = GClass1490; +global using DiscardLimitsDescriptor = GClass1491; +global using SlotDescriptor = GClass1492; +global using ShellTemplateDescriptor = GClass1493; +global using MalfunctionDescriptor = GClass1494; +global using ItemInGridDescriptor = GClass1495; +global using GridDescriptor = GClass1496; +global using StackSlotDescriptor = GClass1497; +global using NestedItemDescriptor = GClass1498; +global using ItemDescriptor = GClass1499; +global using FoodDrinkComponentDescriptor = GClass1501; +global using PoisonComponentDescriptor = GClass1502; +global using ResourceItemComponentDescriptor = GClass1503; +global using LightComponentDescriptor = GClass1504; +global using LockableComponentDescriptor = GClass1505; +global using MapComponentDescriptor = GClass1506; +global using MedKitComponentDescriptor = GClass1507; +global using RepairableComponentDescriptor = GClass1508; +global using SightComponentDescriptor = GClass1509; +global using TogglableComponentDescriptor = GClass1510; +global using FaceShieldComponentDescriptor = GClass1511; +global using FoldableComponentDescriptor = GClass1512; +global using FireModeComponentDescriptor = GClass1513; +global using DogTagComponentDescriptor = GClass1514; +global using TagComponentDescriptor = GClass1515; +global using KeyComponentDescriptor = GClass1516; +global using RepairKitComponentDescriptor = GClass1517; +global using RepairEnhancementComponentDescriptor = GClass1518; +global using RecodableComponentDescriptor = GClass1519; +global using CultistAmuletComponentDescriptor = GClass1520; +global using JsonLootItemDescriptor = GClass1521; +global using JsonCorpseDescriptor = GClass1522; +global using LootDataDescriptor = GClass1523; +global using SlotItemAddressDescriptor = GClass1526; +global using StackSlotItemAddressDescriptor = GClass1527; +global using GridItemAddressDescriptor = GClass1528; +global using OwnerItselfDescriptor = GClass1529; +global using ContainerDescriptor = GClass1530; +global using GTarget42 = GClass1531; +global using AddOperationDescriptor = GClass1533; +global using MagOperationDescriptor = GClass1534; +global using LoadMagOperationDescriptor = GClass1535; +global using UnloadMagOperationDescriptor = GClass1536; +global using RemoveOperationDescriptor = GClass1537; +global using ExamineOperationDescriptor = GClass1538; +global using ExamineMalfunctionOperationDescriptor = GClass1539; +global using ExamineMalfTypeOperationDescriptor = GClass1540; +global using CheckMagazineOperationDescriptor = GClass1541; +global using BindItemOperationDescriptor = GClass1542; +global using UnbindItemOperationDescriptor = GClass1543; +global using InsureItemsOperationDescriptor = GClass1544; +global using MoveOperationDescriptor = GClass1545; +global using MoveAllOperationDescriptor = GClass1546; +global using SplitOperationDescriptor = GClass1547; +global using MergeOperationDescriptor = GClass1548; +global using TransferOperationDescriptor = GClass1549; +global using SwapOperationDescriptor = GClass1550; +global using ThrowOperationDescriptor = GClass1551; +global using ToggleOperationDescriptor = GClass1552; +global using FoldOperationDescriptor = GClass1553; +global using ShotOperationDescriptor = GClass1554; +global using SetupItemOperationDescriptor = GClass1555; +global using ApplyOperationDescriptor = GClass1556; +global using ApplyHealthOperationDescriptor = GClass1557; +global using CreateMapMarkerOperationDescriptor = GClass1558; +global using EditMapMarkerOperationDescriptor = GClass1559; +global using DeleteMapMarkerOperationDescriptor = GClass1560; +global using AddNoteOperationDescriptor = GClass1561; +global using EditNoteOperationDescriptor = GClass1562; +global using DeleteNoteOperationDescriptor = GClass1563; +global using TagOperationDescriptor = GClass1564; +global using OperateStationaryWeaponOperationDescriptor = GClass1565; +global using WeaponRechamberOperationDescriptor = GClass1566; +global using ObservedSyncItemsOperationDescriptor = GClass1567; +global using QuestActionDescriptor = GClass1568; +global using QuestAcceptDescriptor = GClass1569; +global using QuestFinishDescriptor = GClass1570; +global using QuestHandoverDescriptor = GClass1571; +global using InventoryLogicOperationsCreateItemsDescriptor = GClass1572; +global using InventoryLogicOperationsPurchaseTraderServiceOperationDescriptor = GClass1573; +global using NetworkQualityParam = GClass1645; +global using BaseState = GClass1690; +global using FavoriteItems = GClass1745; +global using BuffInfo = EFT.SkillManager.GClass1771; +global using SkillAction = EFT.SkillManager.GClass1780; +global using VoipSettings = GClass1795; +global using AbstractCombination = GClass1893; +global using ResourceConsumer = GClass1903; +global using Data7 = GClass1928; +global using AbstractDialogList = GClass2072; +global using Metrics1 = GClass2109; +global using PhysicalParametersCommandMessage = GClass2240; +global using BundleAnimationBones = GClass2359; +global using GAbstractEffect1 = HealthControllerClass.GClass2421; +global using RemoteEndPoint = GClass2469; +global using GItemType = EItemType; +global using GItemContext2 = GClass2823; +global using AbstractCreateBuildManipulation = GClass2829; +global using GInvokedEvent = GClass2905; +global using Suite = GClass2938; +global using EffectObserver = GClass3100; +global using TreatmentWrapper = GClass3101; +global using MessageData = GClass3174; +global using GesturesStorage = GClass3175; +global using AbstractOfferData = GClass3197; +global using QuestController = GClass3206; +global using TaskConditionCounter = GClass3219; +global using GConditions = GClass3221; +global using AbstractError = GClass3284; +global using ResultCache = GClass340; +global using DataWarn = GClass361; +global using Data8 = GClass454; +global using RequestJson = GClass561; +global using TrySpawnFreeInner = GClass583; +global using Data9 = GClass592; +global using DebugCollector = GClass606; +global using GClass649DisablerCullingObjectTriggers = GClass649.GClass653.GClass656; +global using GClass649VolumePropagationAndEnvironmentSwitcherTriggers = GClass649.GClass653.GClass657; +global using NeighboursColliders = SimpleCharacterController.GClass661; +global using Screen1 = MultiFlare.GClass862; +global using Quote = GClass942; +global using SoundSettings = GClass956; +global using IFrameIndexer1 = GInterface101; +global using IGIController = GInterface125; +global using IGenerator = GInterface162; +global using IRaidCounter = GInterface187; +global using IStimulator = GInterface278; +global using IFrom = GInterface316; +global using IGIOperationResult = GInterface332; +global using IMapEditable = GInterface354; +global using IAssetPool = GInterface359; +global using IGIController1 = GInterface370; +global using IItem = GInterface400; +global using IProvider = GInterface48; +global using IPlayableAnimator = GInterface71; +global using IOriginalNode = GInterface81; +global using IBlendParamsKeeper = GInterface83; +global using DisconnectInfo = GStruct114; +global using GPreviousPacket2 = GStruct119; +global using FirearmPacket = GStruct187; +global using DialogContext = GStruct245; +global using NextModel = GStruct288; +global using EmptyHandPacket = GStruct318; +global using HealthSyncPacket = GStruct346; +global using GStruct346ExtraData = GStruct346.GStruct347.GStruct348; +global using GPiece = GStruct388; +global using Settings5 = GStruct394; +global using Point2 = GStruct419; +global using GStruct74Type = GStruct74.ENodeType; +global using TimeBound = GStruct95; +global using Type2 = LightOverride.Type; +global using QueuedOperation = Class263.Class1310; +global using GAbstractState1 = EFT.ClientPlayer.Class1402.Class1404; +global using GAbstractState2 = Class1787.Class1788; +global using GSourceContext1 = GClass2823.Class2135; +global using ProcessingSlot = GClass2832.Class2144; +global using GItem7 = EFT.UI.EquipmentBuildsScreen.Class2415; +global using GFilterRule = EFT.UI.FilterPanel.Class2426; +global using CamSettings = AmbientLight.Class551; +global using Prototype = SpeedTreeTerrainProcessor.Class583; +global using CreateLightStruct = GClass904.Class631; +global using MeshData = GClass917.Class642; +global using SplitMeshData = GClass1020.Class692; +global using Class824Type = Class824.EType; +global using Socket1 = Class831; +global using GetField = GClass1485.Class939; +global using TypeMetaInfo = GClass1485.Class941; +global using Indices = GClass1004; +global using SamplingTask = GClass1015; +global using Wireframe = GClass1031.RenderMode; +global using IndexDecompressor = GClass1045.GClass1046; +global using GetCellBounds = GClass1045.GStruct78; +global using FastControllerInfo = GClass1139; +global using LayerInfo = GClass1140; +global using FlatFastAnimatorController = GClass1142.GClass1143; +global using ControllerEntity = GClass1142.GClass1144; +global using EventsState = GClass1142.GClass1144.BehType; +global using Motion1 = GClass1142.GClass1145; +global using FlatAnimatorControllerTransition = GClass1142.GClass1146; +global using FlatTranstionCondition = GClass1142.GClass1147; +global using FlatAnimatorParameter = GClass1142.GClass1148; +global using NetworkQuality = GClass1152; +global using GLootItem1 = GClass1200; +global using CreateRaidPlayerInfo = GClass1208; +global using GClass1211Info = GClass1211.GClass1212; +global using Banner = LocationSettingsClass.Location.GClass1222; +global using Settings6 = GClass1225; +global using AbstractNode = GClass1240; +global using DailyQuest = GClass1250; +global using Statistics = GClass1283; +global using Config4 = GClass1303; +global using GClass1303ReleaseProfiler = GClass1303.GClass1304; +global using ClientSettingsNetworkStateView = GClass1303.GClass1306; +global using PointsRate = BackendConfigSettingsClass.GClass1313.GClass1314; +global using BackendConfigSettingsClassEliteSlots = BackendConfigSettingsClass.GClass1313.GClass1315; +global using Revolver = BackendConfigSettingsClass.GClass1326; +global using Pistol = BackendConfigSettingsClass.GClass1327; +global using Assault = BackendConfigSettingsClass.GClass1328; +global using Shotgun = BackendConfigSettingsClass.GClass1329; +global using Sniper = BackendConfigSettingsClass.GClass1330; +global using BackendConfigSettingsClassBonusSettings = BackendConfigSettingsClass.GClass1338.GClass1339; +global using LevelBonusSettings = BackendConfigSettingsClass.GClass1338.GClass1340; +global using EliteBonusSettings = BackendConfigSettingsClass.GClass1338.GClass1341; +global using BackendConfigSettingsClassKill = BackendConfigSettingsClass.GClass1353.GClass1355; +global using BackendConfigSettingsClassLevel = BackendConfigSettingsClass.GClass1353.GClass1357; +global using BackendConfigSettingsClassHeal = BackendConfigSettingsClass.GClass1353.GClass1358; +global using BackendConfigSettingsClassMatchEnd = BackendConfigSettingsClass.GClass1353.GClass1359; +global using AbstractGetAiming = GClass136; +global using BackendConfigSettingsClassFalling = BackendConfigSettingsClass.GClass1360.GClass1361; +global using BackendConfigSettingsClassHealPrice = BackendConfigSettingsClass.GClass1360.GClass1362; +global using GetArmorClass = BackendConfigSettingsClass.GClass1364.GClass1365; +global using BackendConfigSettingsClassEnvironmentSettings = BackendConfigSettingsClass.GClass1379.GClass1381; +global using CustomizationSolver = GClass1444; +global using ChannelCombined = GClass1638; +global using NetworkQualityParams = GClass1644; +global using CommonPacket = GClass1765; +global using SkillInfo = GClass1783.GClass1784; +global using GetCounter = SessionCountersClass.GClass1787; +global using StartDiff = GClass18.GClass19; +global using LoadScenesFromPreset = GClass1804; +global using HideoutController = EFT.TarkovApplication.GClass1816; +global using NewLocale = GClass1852; +global using Commodity = GClass1867; +global using GetSphereIntersection = GClass1873.IntersectionType; +global using Stored = GClass1882; +global using GKeyCombination1 = GClass1896; +global using GScheme = GClass1924; +global using AbstractBehaviour = GClass1927; +global using AbstractRequirement = GClass1934; +global using TargetControl = GClass1951; +global using FriendRequestCanceled = GClass1998; +global using AbstractDialogController = GClass2051; +global using History1 = GClass2055; +global using StartWithToken = GClass21.GStruct3; +global using GameUpdateBinMetricCollector = GClass2115; +global using RenderBinMetricCollector = GClass2123; +global using GridOptionsPageView = GClass2165; +global using MainInfoPageView = GClass2166; +global using StartPageView = GClass2168; +global using BeginSampleWithToken = GClass22.GStruct4; +global using CommandMessage = GClass2225; +global using DeathCommand = GClass2226; +global using EffectOnPlayerStatusCommandMessage = GClass2230; +global using GCommandMessage = GClass2235; +global using GCommandMessage1 = GClass2241; +global using GCommandMessage2 = GClass2246; +global using SkillsParamsCommandMessage = GClass2250; +global using GCommandMessage3 = GClass2254; +global using CreateInstance = GClass2362; +global using ObservedPlayerControllers = GClass2363; +global using InfoContainer = GClass2379; +global using GProfile1 = GClass2380; +global using AbstractHealth = GClass2417; +global using GClass2424Existence = GClass2424.GClass2425; +global using GClass2424Dehydration = GClass2424.GClass2426; +global using GClass2424LightBleeding = GClass2424.GClass2427; +global using GClass2424Fracture = GClass2424.GClass2428; +global using GClass2424Contusion = GClass2424.GClass2429; +global using GClass2424Disorientation = GClass2424.GClass2430; +global using GClass2424Exhaustion = GClass2424.GClass2431; +global using GClass2424LowEdgeHealth = GClass2424.GClass2432; +global using GClass2424RadExposure = GClass2424.GClass2433; +global using GClass2424Stun = GClass2424.GClass2434; +global using GClass2424Intoxication = GClass2424.GClass2435; +global using GClass2424LethalIntoxication = GClass2424.GClass2436; +global using GClass2424Regeneration = GClass2424.GClass2438; +global using GClass2424Wound = GClass2424.GClass2441; +global using GClass2424ChronicStaminaFatigue = GClass2424.GClass2442; +global using GClass2424Berserk = GClass2424.GClass2443; +global using GClass2424Flash = GClass2424.GClass2444; +global using GClass2424MedEffect = GClass2424.GClass2445; +global using GClass2424Pain = GClass2424.GClass2446; +global using GClass2424PainKiller = GClass2424.GClass2447; +global using GClass2424SandingScreen = GClass2424.GClass2448; +global using GClass2424Stimulator = GClass2424.GClass2449; +global using GClass2424Tremor = GClass2424.GClass2451; +global using GClass2424BodyTemperature = GClass2424.GClass2452; +global using GClass2424HealthBoost = GClass2424.GClass2453; +global using GClass2424MildMusclePain = GClass2424.GClass2455; +global using Effect1 = GClass2461; +global using BuffType = EFT.SkillManager.EBuffType; +global using ItemCollection = GClass2504; +global using CurrencyData = GClass2517.GClass2518; +global using FastAccess = GClass2519; +global using ParentTemplate = GClass2533; +global using GItem11 = GClass2704; +global using GItem12 = GClass2735; +global using WeaponMalfState = EFT.InventoryLogic.Weapon.GClass2742; +global using Component1 = GClass2754; +global using GInventoryController1 = GClass2762; +global using SlotItemAddress = GClass2767; +global using AddResult = GClass2782; +global using ChangeVersionResult = GClass2784; +global using MoveOldMagResult = GClass2786; +global using IGISelectionContext = GClass2821.GInterface336; +global using GSourceContext2 = GClass2826; +global using ExamineOperation = GClass2857; +global using ToggleItem = GClass2875; +global using GClass2892ZoneState = GClass2892.EZoneState; +global using GInvokedEvent1 = GClass2899; +global using FlareEvent = GClass2900; +global using GInvokedEvent2 = GClass2903; +global using FiredEvent = GClass2912; +global using RaisedEvent = GClass2914; +global using Root1 = GClass2970; +global using TrajectoryInfo = GClass2989; +global using MaxPoint = GClass299; +global using LoadScene = GClass2999; +global using Button1 = GClass3039.EEmptyEnum; +global using ContextInteractions = GClass3046; +global using RowInfo = GClass3092; +global using Price = GClass3093; +global using Section1 = GClass3103; +global using ReconnectionScreenController = EFT.UI.ReconnectionScreen.GClass3129; +global using GScreenController = EFT.UI.Matchmaker.MatchMakerAcceptScreen.GClass3150; +global using GScreenController1 = EFT.UI.Settings.SetNicknameScreen.GClass3159; +global using GScreenController2 = EFT.UI.Settings.SettingsScreen.GClass3160; +global using SettingsScreenTempSettings = EFT.UI.Settings.SettingsScreen.GClass3160.GClass3165; +global using Dialog = GClass3172; +global using LoadingBundle = GClass3187.GClass3188; +global using Merchant = EFT.UI.Ragfair.Offer.GClass3192; +global using GCondition = GClass3215; +global using GBeginSampleWithToken = GClass3257.GStruct409; +global using ReleaseBeginSampleWithToken = GClass3257.GStruct410; +global using Answer = GClass358; +global using BossData = GClass373; +global using BossKojaniy = GClass376; +global using GoalTarget = GClass389; +global using BossPatrolMoveSimple = GClass432; +global using EnemyInfo1 = GClass463; +global using AbstractPath = GClass466; +global using GrenadeDangerPoint = GClass498; +global using CheckVisibilityPart = GClass521; +global using LookAll = GClass522; +global using GClass559Logger = GClass559.GClass617; +global using GError = GClass562.GClass563; +global using OrderType = GClass567.EOrderType; +global using BornInfo = GClass590; +global using CreateContainerForBatch = GClass642.GClass643; +global using Scheduled = GClass648.GStruct29; +global using GClass649UpdateMode = GClass649.GClass650.UpdateModeType; +global using PlayerFrame = GClass667; +global using GetViewForSlot = GClass674.GClass675; +global using CreateGrenadeFactory = GClass676; +global using ConsumptionType = GClass682.EConsumptionType; +global using StatItem = ProfileStats.GClass693; +global using GetCounters = GClass732.GStruct44; +global using Settings8 = GClass738.GStruct45; +global using TurnSide = GClass759.SideTurn; +global using GClass772Settings = GClass772.MemoryManagementSettings; +global using GMeshData = GClass781.GStruct51; +global using TriangleFill = GClass781.GStruct53.NativeMeshRenderMode; +global using GRepairKit = GClass802; +global using HealthController = GClass814; +global using ItemIcon = GClass821; +global using PixelationMode1 = Pixelation.PixelationMode; +global using Sqrt = GClass892; +global using Node3 = GClass895.GStruct64; +global using SystemData = GClass934; +global using Settings9 = GClass957; +global using IUpdatable = GInterface10; +global using IInteractive = GInterface102; +global using IView = GInterface104; +global using IMedsController = GInterface130; +global using IQuickKnifeKickController = GInterface134; +global using IQuickUseController = GInterface135; +global using IProfileUpdater = GInterface142; +global using ICaptchaHandler = GInterface143; +global using IPlayerInputTranslator = GInterface151; +global using IHandsInputTranslator = GInterface152; +global using IExchangeable = GInterface153; +global using ISession3 = GInterface16; +global using IToken = GInterface19; +global using IMovementContext = GInterface192; +global using IKinematicModel = GInterface215; +global using IGIFrom = GInterface225; +global using IEventsConsumer = GInterface24; +global using IRepairStrategy = GInterface33; +global using IShaderReplacer = GInterface36; +global using IRoleModel = GInterface360; +global using IViewFactory = GInterface367; +global using IFactorObserver = GInterface374; +global using IEvent = GInterface389; +global using IWinterMaterial = GInterface44; +global using IPoint = GInterface6; +global using ICreateSamplingTaskResult = GInterface62; +global using ISamplingProvider = GInterface63; +global using IExtension = GInterface65; +global using ILayerProcessor = GInterface73; +global using IBitReader = GInterface75; +global using IInfo = BackendConfigSettingsClass.GInterface95; +global using HeathsData = GStruct13; +global using GameSent = GStruct131; +global using PrevFrame = GStruct162; +global using MovementInfoPacket = GStruct165; +global using PacketItemInteraction = GStruct167; +global using StationaryWeaponPacket = GStruct169; +global using GStruct169StationaryCommand = GStruct169.EStationaryCommand; +global using PlantItemPacket = GStruct170; +global using VaultingPacket = GStruct171; +global using LootInteractionPacket = GStruct173; +global using HandsChangePacket = GStruct178; +global using ReloadMagPacket1 = GStruct189; +global using QuickReloadMag = GStruct190; +global using InventoryCommandPacket = GStruct196; +global using GPrevFrame2 = GStruct215; +global using ConditionalData = GStruct222; +global using GResourceKey = PoolManager.GStruct229; +global using Type3 = GStruct247.ESaveStateType; +global using ObservedUsableItemUpdatedData = GStruct283; +global using CorpseImpulse = GStruct316; +global using GPacket4 = GStruct337; +global using GOther12 = GStruct339; +global using GStruct346ExtraDataType = GStruct346.GStruct347.EExtraDataType; +global using GStruct346Data2 = GStruct346.GStruct349; +global using GStruct346Data3 = GStruct346.GStruct350; +global using GStruct346Data4 = GStruct346.GStruct351; +global using GStruct346Data5 = GStruct346.GStruct352; +global using GStruct346Data6 = GStruct346.GStruct353; +global using GStruct346Data7 = GStruct346.GStruct354; +global using GStruct346Data8 = GStruct346.GStruct355; +global using GStruct346Data9 = GStruct346.GStruct356; +global using GStruct346Data10 = GStruct346.GStruct358; +global using GStruct346Data11 = GStruct346.GStruct359; +global using GStruct346Data12 = GStruct346.GStruct360; +global using GStruct346Data13 = GStruct346.GStruct361; +global using GStruct346Data14 = GStruct346.GStruct362; +global using GStruct346Data15 = GStruct346.GStruct363; +global using GStruct346Data16 = GStruct346.GStruct364; +global using GStruct346Data17 = GStruct346.GStruct365; +global using SpawnSystemSettings = GStruct380; +global using GetStatusInfo = EFT.Interactive.WorldInteractiveObject.GStruct385; +global using Article = GStruct398; +global using Click = EFT.UI.Gestures.GestureBaseItem.GStruct400; +global using ChangeRequirement = GStruct405; +global using QuestChangeCost = GStruct406; +global using ToPoint = GStruct418; +global using ChangeValue = GStruct50; +global using Query1 = GStruct77; +global using Data13 = GStruct79; +global using ScaleToVector2Byte = GStruct86; +global using ScaleToVector2Short = GStruct87; +global using ScaleToVector3Byte = GStruct88; +global using ScaleToVector3Short = GStruct89; +global using Last = GStruct92; +global using Serializer1 = GClass1201; +global using Creating = EFT.Player.Class1058.InternalState; +global using SetInHandsOperation = EFT.Player.Class1059; +global using ISession4 = Class1370.Interface8; +global using RcMonitor = EFT.Settings.Graphics.EftDisplay.Class1530.Struct451; +global using Point4 = EFT.Settings.Graphics.EftDisplay.Class1530.Struct452; +global using LpfnEnum = EFT.Settings.Graphics.EftDisplay.Class1530.Delegate12; +global using SourceInfo1 = EFT.Settings.Graphics.EftDisplay.Class1534.Struct454; +global using TargetInfo = EFT.Settings.Graphics.EftDisplay.Class1534.Struct455; +global using HSyncFreq = EFT.Settings.Graphics.EftDisplay.Class1534.Struct456; +global using ActiveSize = EFT.Settings.Graphics.EftDisplay.Class1534.Struct458; +global using TargetVideoSignalInfo = EFT.Settings.Graphics.EftDisplay.Class1534.Struct459; +global using TargetMode = EFT.Settings.Graphics.EftDisplay.Class1534.Struct460; +global using SourceMode = EFT.Settings.Graphics.EftDisplay.Class1534.Struct462; +global using ModeInfo = EFT.Settings.Graphics.EftDisplay.Class1534.Struct463; +global using Flags1 = EFT.Settings.Graphics.EftDisplay.Class1534.Struct465; +global using Header1 = EFT.Settings.Graphics.EftDisplay.Class1534.Struct466; +global using IDrawInstance = Class1780.Interface15; +global using Stopping = Class259.EBackEndCoreState; +global using Warning = Class263.InventoryWarning; +global using SpawnProcessData = BossSpawnerClass.Class288; +global using Columns = Class3183; +global using MRows = Class3184; +global using GMesh = Class3192; +global using Data15 = Class3197.EventArgs0; +global using Element1 = Class3198.Struct1067; +global using AnimationEventsContainerLogger = AnimationEventSystem.AnimationEventsContainer.Class338; +global using WinterEventController = Class420; +global using Triangle1 = RoadsTerrainAligner.Class513; +global using XAlig = Class608.XAlignment; +global using YAlig = Class608.YAlignment; +global using JobParams = Koenigz.PerfectCulling.EFT.PerfectCullingCrossSceneSampler.Class718; +global using AbstractChannel = Class818; +global using GPacket6 = Class828; +global using AdvAssaultTarget = Class95; +global using SimpleTarget = Class96; +global using ObdolbosFight = Class97; +global using LeaveMap = Class98; +global using Pursuit = Class99; +global using AddPoint = GClass0; +global using Retrieve = GraphManager.GClass10; +global using MarksmanTarget = GClass100; +global using AllocateExchangeBuffer = GClass1005; +global using Panic = GClass101; +global using WriteMode = CC_Glitch.Mode; +global using Utilitypeace = GClass102; +global using RuntimeBVH = GClass1027; +global using PatrolAssault = GClass103; +global using Baker1 = GClass1031; +global using Callback1 = GClass1031.GDelegate31; +global using Sampler = GClass1033; +global using StayAtPos = GClass104; +global using Streamer1 = GClass1042; +global using PackedCullingGridData = GClass1045; +global using OutOfBounds = Koenigz.PerfectCulling.EFT.PerfectCullingCrossSceneVolume.GClass1049.EUpdateResult; +global using AbstractSpCell = GClass1052; +global using CreateClient = GClass1070; +global using CreateServer = GClass1071; +global using StorageDeviceProperty = GClass1075.StoragePropertyId; +global using PropertyStandardQuery = GClass1075.StorageQueryType; +global using Followerbully = GClass108; +global using SecurityGluhar = GClass109; +global using GraphManagerGraph = GraphManager.GClass11; +global using Between = GClass1101.ERangeType; +global using AbstractParentStateMachine = GClass1109; +global using BossSanitarFight = GClass111; +global using ChildInnerStateMachine = GClass1113; +global using ControllerState = GClass1117; +global using GControllerState = GClass1118; +global using FlSanFight = GClass112; +global using Undefined = GClass1120.EAnimatorValueType; +global using Behavior1 = GClass1124; +global using InternalCreate = GClass1125; +global using GOther13 = GClass1126; +global using GOther14 = GClass1127; +global using SanitarGoal = GClass113; +global using GCondition1 = GClass1141; +global using EqualInt = GClass1141.EConditionDelegateType; +global using GrenSuicide = GClass115; +global using PacketsQueueCount = GClass1150; +global using UpdateCountMeasurer = GClass1155; +global using MaxPacketsQueueCount = GClass1156; +global using FixedUpdatesBetweenUpdateMeasurer = GClass1157; +global using NetOutgoingMeasurer = GClass1158; +global using Settings11 = GClass1174; +global using Settings12 = GClass1177; +global using RunStrike = GClass118; +global using HalloweenEvent = GClass1186; +global using WinterEvent = GClass1187; +global using AirdropEvent = GClass1188; +global using BackendUrls = GClass1191; +global using TagillaAmbush = GClass121; +global using TagillaFollower = GClass122; +global using ItemReference = GClass1226; +global using TagillaMain = GClass123; +global using Ambush = GClass123.ETagillaBattleLogic; +global using DamageEffect = GClass1235; +global using CutPiece = GClass1236; +global using TestLayer = GClass124; +global using GNodeType = GClass1240.EComputedNodeType; +global using CustomBlendTree = GClass1244; +global using BlendType1 = GClass1244.EBlendType; +global using GTestLayer = GClass125; +global using BytesSegment = GClass1255; +global using GClass1259SwitchState = GClass1259.State; +global using SystemDelegate = GClass1274.Delegate5; +global using NatPunchModule = GClass1278; +global using CloseZrFight = GClass128; +global using AdditionalData = GClass1288; +global using ZryachiyFight = GClass129; +global using GetCallbackFromData = GClass1290.SubscribeDelegate; +global using AbstractExtraPacketLayer = GClass1293; +global using FlZryachFight = GClass130; +global using LayingPatrol = GClass131; +global using HideoutManagement = BackendConfigSettingsClass.GClass1313; +global using Crafting = BackendConfigSettingsClass.GClass1316; +global using Metabolism = BackendConfigSettingsClass.GClass1317; +global using Immunity = BackendConfigSettingsClass.GClass1318; +global using Endurance1 = BackendConfigSettingsClass.GClass1319; +global using CheckZryachiy = GClass132; +global using Strength = BackendConfigSettingsClass.GClass1320; +global using Vitality = BackendConfigSettingsClass.GClass1321; +global using BackendConfigSettingsClassHealth = BackendConfigSettingsClass.GClass1322; +global using StressResistance = BackendConfigSettingsClass.GClass1323; +global using GThrowing = BackendConfigSettingsClass.GClass1324; +global using RecoilControl = BackendConfigSettingsClass.GClass1325; +global using StandBy = GClass133; +global using CovertMovement = BackendConfigSettingsClass.GClass1332; +global using MagDrills = BackendConfigSettingsClass.GClass1334; +global using Perception = BackendConfigSettingsClass.GClass1335; +global using Intellect = BackendConfigSettingsClass.GClass1336; +global using Attention = BackendConfigSettingsClass.GClass1337; +global using Charisma = BackendConfigSettingsClass.GClass1338; +global using AbstractCreateNode = GClass134; +global using Surgery = BackendConfigSettingsClass.GClass1343; +global using AimDrills = BackendConfigSettingsClass.GClass1344; +global using WeaponTreatment = BackendConfigSettingsClass.GClass1346; +global using TroubleShooting = BackendConfigSettingsClass.GClass1347; +global using LightVests = BackendConfigSettingsClass.GClass1348; +global using HeavyVests = BackendConfigSettingsClass.GClass1349; +global using BackendConfigSettingsClassRepairSettings = BackendConfigSettingsClass.GClass1350; +global using BackendConfigSettingsClassExperience = BackendConfigSettingsClass.GClass1353; +global using BackendConfigSettingsClassHealth1 = BackendConfigSettingsClass.GClass1360; +global using BackendConfigSettingsClassProfileHealthSettings = BackendConfigSettingsClass.GClass1360.PlayerHealthFactorsSettings; +global using BackendConfigSettingsClassArmor = BackendConfigSettingsClass.GClass1364; +global using BackendConfigSettingsClassStaminaRestoration = BackendConfigSettingsClass.GClass1367; +global using BackendConfigSettingsClassStamina = BackendConfigSettingsClass.GClass1368; +global using MalfSettings = BackendConfigSettingsClass.GClass1370; +global using BackendConfigSettingsClassBallistic = BackendConfigSettingsClass.GClass1372; +global using BackendConfigSettingsClassAirdrop = BackendConfigSettingsClass.GClass1373; +global using Settings14 = BackendConfigSettingsClass.GClass1374; +global using BackendConfigSettingsClassCoopSettings = BackendConfigSettingsClass.GClass1375; +global using BackendConfigSettingsClassEventSettings = BackendConfigSettingsClass.GClass1376; +global using BackendConfigSettingsClassItemsSettings = BackendConfigSettingsClass.GClass1377; +global using BackendConfigSettingsClassWeaponFastDrawGlobalSettings = BackendConfigSettingsClass.GClass1378; +global using BackendConfigSettingsClassAudioSettings = BackendConfigSettingsClass.GClass1379; +global using BackendConfigSettingsClassGraphicGlobalSettings = BackendConfigSettingsClass.GClass1383; +global using BackendConfigSettingsClassFavoriteItemsSettings = BackendConfigSettingsClass.GClass1384; +global using GHandbook = GClass1385; +global using GItem18 = GClass1394; +global using GridInfo = GClass1395; +global using Loop = GClass14.DetectType; +global using GClass1401ZoneType = GClass1401.EQuestZoneType; +global using TrafficData = GClass1480; +global using WeatherSettings = GClass1482; +global using GAbstractDescriptor1 = GClass1500; +global using InsertMagResult = EFT.Player.FirearmController.GClass1576; +global using ReloadExternalMagResult = EFT.Player.FirearmController.GClass1577; +global using ReloadMultiBarrelResult = EFT.Player.FirearmController.GClass1579; +global using ReloadSingleBarrelResult = EFT.Player.FirearmController.GClass1580; +global using AbstractNextOperation = EFT.Player.GClass1583; +global using Channels1 = GClass1639; +global using DataBegin = GClass1641.EIncomeMessageType; +global using Interpolator = GClass1651; +global using DeferredData = GClass1653; +global using ApplyDelegate = GClass1653.Delegate10; +global using Pedometer = GClass1658; +global using ObjectInHands = GClass1667; +global using AbstractMagazineInHandsVisual = GClass1669; +global using EndState = GClass1684; +global using GStage = GClass1700.EStationaryStage; +global using PushingFromTheGround = GClass1703.EJumpState; +global using SpeedLimiterBuilder = GClass1738; +global using GEnergy = EFT.Profile.GClass1756.ValueInfo; +global using GetBan = GClass1759; +global using LightVestsDamageReduction = EFT.SkillManager.GClass1774; +global using SkillManagerImmunityAvoidPoisonChance = EFT.SkillManager.GClass1775; +global using SkillManagerMetabolismRatioPlus = EFT.SkillManager.GClass1776; +global using SkillManagerMetabolismMiscDebuffTime = EFT.SkillManager.GClass1777; +global using NoWearEliteRepair = EFT.SkillManager.GClass1778; +global using VoipQualitySettings = GClass1796; +global using PushToTalkSettings = GClass1797; +global using AddNewGroup = GClass1808; +global using GHardwareDescription = GClass1810; +global using GExecute = GClass1818; +global using GExecute1 = GClass1819; +global using GExecute2 = GClass1821; +global using StashChanges = GClass1846; +global using GSlotView = EFT.PlayerBody.GClass1860; +global using GItem19 = GClass1863; +global using RagfairInfo = GClass1864; +global using Ultra = GClass1881; +global using GetUpdater = GClass1889; +global using GetAxis = GClass1892; +global using Side = GClass1894.EUseSide; +global using State2 = GClass1894.EAxisState; +global using NextStateName = GClass1895.EKeyState; +global using GetKey = GClass1897; +global using GGetUpdater = GClass1899; +global using Supply = GClass1905; +global using Settings15 = GClass1908; +global using GImprovement = GClass1913; +global using ScavProducer = GClass1919; +global using EndProducts = GClass1925; +global using DetailsGroup = GClass1929; +global using GQteResult = GClass1937; +global using NewFriend = GClass1997; +global using NotificationMatchRaidSettings = GClass2005; +global using NotificationGroupMatchRaidReady = GClass2006; +global using NotificationGroupMatchRaidNotReady = GClass2007; +global using AbstractNotification = GClass2037; +global using AbstractOther = GClass2041; +global using AbstractAcceptServiceLine = GClass2059; +global using AbstractQuestLine = GClass2062; +global using AbstractServiceDialogDeal = GClass2081; +global using AbstractQuestDialog = GClass2084; +global using FixedUpdateBinMetricCollector = GClass2112; +global using FrameBinMetricCollector = GClass2113; +global using FrameWithoutFixedUpdatesBinMetricCollector = GClass2114; +global using MaxClientServerTimeDiffBinMetricCollector = GClass2116; +global using MaxClientServerTimeDiffByPlayerBinMetricCollector = GClass2117; +global using MaxLossBinMetricCollector = GClass2118; +global using MaxPacketsQueueCountBinMetricCollector = GClass2119; +global using MaxPacketsQueueTimeBinMetricCollector = GClass2120; +global using MaxRttBinMetricCollector = GClass2121; +global using ReceivedPacketsCountBinMetricCollector = GClass2122; +global using UnprocessedPacketsCountBinMetricCollector = GClass2124; +global using MemoryMetricCollector = GClass2125; +global using MetricsCollector = GClass2128; +global using MetricsConfig = GClass2130; +global using SharedSettings = GClass2136; +global using ProcessorCore = GClass2140.Struct659; +global using NumaNode = GClass2140.Struct660; +global using ProcessorInformation = GClass2140.Struct662; +global using ObservedVaultingParameters = GClass2156; +global using View1 = GClass2167; +global using SimpleProfiler = GClass22; +global using KinematicController = GClass2209; +global using AutoSender = GClass2214; +global using ArmorDurabilityChangedCommandMessage = GClass2219; +global using ArmorInfoCommandMessage = GClass2220; +global using BtrGoInInteractionMessage = GClass2221; +global using BtrGoOutInteractionMessage = GClass2222; +global using ChangeEquipCommandMessaged = GClass2223; +global using GCreateInstance8 = GClass2224; +global using DoorBreachInteractionMessage = GClass2227; +global using DoorInteractionMessage = GClass2228; +global using GClass2230ChangedEffectType = GClass2230.EHealthEffectType; +global using GestureCommandMessage = GClass2232; +global using HeadDeviceStatusCommandMessage = GClass2233; +global using GClass2233DeviceType = GClass2233.EHeadDeviceType; +global using HealthStatusCommandMessage = GClass2234; +global using GCommandMessage4 = GClass2237; +global using MedEffectStatusCommandMessage = GClass2238; +global using PhraseCommandMessage = GClass2239; +global using GCreateInstance21 = GClass2242; +global using SetAnimatorLayerWeightCommand = GClass2243; +global using HandsCommandMessage = GClass2244; +global using SetLeftStanceCommandMessage = GClass2245; +global using UnderRoofStatusMessage = GClass2247; +global using VoiceMuffledStatusMessage = GClass2248; +global using ContentMessage = GClass2251; +global using GCommandMessage5 = GClass2253; +global using GCreateInstance23 = GClass2255; +global using TakeDamageCommandMessage = GClass2256; +global using TemperatureCommandMessage = GClass2257; +global using GCommandMessage6 = GClass2258; +global using VaultingCommandMessage = GClass2260; +global using VoIPCommandMessage = GClass2261; +global using GCreateInstance28 = GClass2265; +global using GCommandMessage7 = GClass2284; +global using GCommandMessage8 = GClass2286; +global using GCreateInstance29 = GClass2294; +global using CreateInstanceValue = GClass2308; +global using GCreateInstance31 = GClass2314; +global using GCreateInstance32 = GClass2315; +global using GCreateInstance33 = GClass2316; +global using GCreateInstance34 = GClass2317; +global using GCreateInstance35 = GClass2318; +global using GCreateInstance36 = GClass2319; +global using GCreateInstance37 = GClass2320; +global using GCreateInstance38 = GClass2321; +global using GCreateInstance39 = GClass2322; +global using GCreateInstance40 = GClass2323; +global using GCreateInstance41 = GClass2324; +global using GCreateInstance42 = GClass2325; +global using GCreateInstance43 = GClass2326; +global using GCreateInstance44 = GClass2327; +global using GCreateInstance45 = GClass2328; +global using GCreateInstance46 = GClass2329; +global using GCreateInstance47 = GClass2331; +global using GCreateInstance48 = GClass2333; +global using GCreateInstance49 = GClass2334; +global using GCreateInstance50 = GClass2335; +global using GCreateInstance51 = GClass2336; +global using GCreateInstance52 = GClass2337; +global using GCreateInstance53 = GClass2338; +global using GCreateInstance54 = GClass2339; +global using GCreateInstance55 = GClass2340; +global using GCreateInstance56 = GClass2341; +global using GCreateInstance57 = GClass2342; +global using GCreateInstance58 = GClass2343; +global using GCreateInstance59 = GClass2344; +global using GCreateInstance60 = GClass2345; +global using GCreateInstance61 = GClass2346; +global using GCreateInstance62 = GClass2347; +global using GCreateInstance63 = GClass2348; +global using GCreateInstance64 = GClass2349; +global using GCreateInstance65 = GClass2350; +global using GCreateInstance66 = GClass2351; +global using GCreateInstance67 = GClass2352; +global using GCreateInstance68 = GClass2353; +global using GCreateInstance69 = GClass2354; +global using GCreateInstance70 = GClass2355; +global using GCreateInstance71 = GClass2356; +global using GCreateInstance72 = GClass2357; +global using AnimationStateHashDictionary = GClass2358; +global using ArmorInfoController = GClass2360; +global using EquipmentViewController = GClass2366; +global using AbstractCreateInstance = GClass2368; +global using GCreateInstance73 = GClass2369; +global using GCreateInstance74 = GClass2370; +global using GCreateInstance75 = GClass2371; +global using GCreateInstance76 = GClass2372; +global using GCreateInstance77 = GClass2373; +global using GCreateInstance78 = GClass2374; +global using GCreateInstance79 = GClass2375; +global using MovementController = GClass2386; +global using ObservedPlayerStateContext = GClass2387; +global using PlayerCullingController = GClass2389; +global using GClass2389Mode = GClass2389.EMode; +global using GEffects = GClass2424; +global using Configuration1 = GClass2471; +global using GAbstractState4 = GClass2473; +global using Received = GClass2487; +global using Disordered = GClass2488; +global using Lose = GClass2489; +global using ReceivedQueue = GClass2490; +global using AbstractAiCoreAgentM = GClass25; +global using GItem20 = GClass2507; +global using LowMute = GClass2542; +global using LauncherTemplate = GClass2571; +global using GItem22 = GClass2733; +global using Enhancement = GClass2750; +global using MaxDurabilityAttribute = GClass2752; +global using TraderControllerClassLogger = TraderControllerClass.GClass2765; +global using GLocation1 = GClass2766; +global using StackSlotItemAddress = GClass2768; +global using FromGridLocationAndSize = GClass2773; +global using LoadMegResult = GClass2790; +global using TransferResult = GClass2796; +global using CreateChildFrom = GClass2817; +global using RepairItemContext = GClass2824; +global using GSourceContext4 = GClass2827; +global using GItemContext4 = GClass2828; +global using CreateOtherManipulation = GClass2830; +global using ToBaseInventoryCommand = GClass2833; +global using FoldOperation = GClass2858; +global using GoInEvent = GClass2891; +global using ZoneEvent = GClass2892; +global using CutsceneEvent = GClass2895; +global using EventType1 = GClass2899.EZoneEventType; +global using HeadphonesUpdateEvent = GClass2901; +global using GInvokedEvent4 = GClass2902; +global using GInvokedEvent5 = GClass2904; +global using GRaisedEvent = GClass2906; +global using GRaisedEvent1 = GClass2907; +global using GInvokedEvent6 = GClass2908; +global using EndCutsceneEvent = GClass2911; +global using GCalledEvent = GClass2913; +global using NoQuests = GClass2914.EDialogState; +global using News1 = GClass2914.EBtrNewsDialogState; +global using SpawnPoints = GClass2928; +global using OnlineDependenceSettings = GClass2932; +global using Bool1 = GClass2934.Struct775.EValueType; +global using GetAnyCustomizationItem = GClass2936; +global using GetVoice = GClass2941; +global using GetIcon = GClass2942; +global using OpticCameraManager = GClass2944; +global using GAbstractInvoke = GClass2945; +global using ReflexController = GClass2949; +global using GFollowerLogic = GClass295; +global using AbstractCreateReferenceTree = GClass2966; +global using RequestHandler = GClass2969.GDelegate72; +global using SegmentOpen = GClass297; +global using GAbstractCreateReferenceTree = GClass2973; +global using MeshPiece = EFT.Interactive.WindowBreakingConfig.GClass2976; +global using Randoms = GClass2979; +global using IconsLoader = GClass3010; +global using Interaction5 = GClass3043.EMagInteraction; +global using Interaction6 = GClass3044.EMagPresetInteraction; +global using Interaction7 = GClass3050.EOfferIdContextInteraction; +global using ContextInteractionsSwitcher = GClass3052; +global using NewOfferContext = GClass3069; +global using Pair1 = GClass307; +global using GetNextPoint = GClass308; +global using GShow1 = GClass3088; +global using ShowEditBuildNameWindow = GClass3089; +global using KeyBinding = GClass3095; +global using AssaultBuilding = GClass31; +global using StatGroup = EFT.UI.SessionEnd.SessionResultStatistics.GClass3105; +global using ScreenManager = GClass3107; +global using AbstractController = EFT.UI.BattleUIScreen.GClass3112; +global using GAbstractController = EFT.UI.InventoryScreen.GClass3116; +global using GAbstractController1 = EFT.UI.MenuScreen.GClass3122; +global using DialogController = EFT.UI.TraderDialogScreen.GClass3132; +global using GAbstractController2 = EFT.UI.TradingScreen.GClass3134; +global using ValidateDeviceIdScreenController = EFT.UI.ValidateDeviceIdScreen.GClass3139; +global using SideSelectionScreenController = EFT.UI.SideSelectionScreen.GClass3143; +global using WelcomeScreenController = EFT.UI.WelcomeScreen.GClass3144; +global using BannerWithToggle = GClass3169; +global using GetInsurePrice = GClass3177; +global using SelectedSummary = GClass3178; +global using InsureSummary = GClass3179; +global using WeaponBuildsStorage = GClass3185; +global using CancellableFilters = GClass3195; +global using Data21 = GClass3198; +global using EnemyBuilding = GClass32; +global using GHydration1 = GClass3217; +global using Daytime = GClass3218; +global using DebugCoverPointsDataCollector = GClass322; +global using Durability = GClass3220; +global using NewQuest = GClass3248; +global using GlobalQuestTemplates = GClass3250; +global using GlobalAchievementTemplates = GClass3251; +global using Point5 = GClass326; +global using GoalBuilding = GClass33; +global using JobSchedulerWorkingStats = Diz.Jobs.JobScheduler.GClass3351; +global using AvgWorkCpuLoad = GClass3353; +global using DebugMemory = GClass336; +global using AchievementsBook = GClass3363; +global using HorizontalFilters = GClass3365; +global using ToDoubleVector4 = GClass3395; +global using AssaultHaveEnemy = GClass34; +global using GMesh1 = GClass3400; +global using Compression1 = GClass3404.Compression; +global using Data22 = GClass341; +global using DebugCoversChecker = GClass343; +global using DecisionProxy = GClass344; +global using BotsConnections = GClass345; +global using AvoidDanger = GClass35; +global using AbstractBaseReachablEditor = GClass351; +global using WayStarCalc = GClass353; +global using CoverWithPath = GClass356; +global using BotCurrentCoverInfo = GClass357; +global using CurRequest = GClass359; +global using BoarGrenadeDanger = GClass36; +global using BaseBTR = GClass37; +global using GBossData = GClass371; +global using SuppressBTR = GClass38; +global using FindPlaceToShoot = GClass409; +global using BoarClPatrol = GClass41; +global using GFirstAid = GClass412; +global using SurgicalKit = GClass415; +global using Stimulators = GClass417; +global using BoarPatrol = GClass42; +global using PointControl = GClass426; +global using FollowerPlayerBase = GClass427; +global using BsBoarPatrol = GClass43; +global using PatrolPathControl = GClass431; +global using BossStayAtPlace = GClass435; +global using MoveByReservWay = GClass437; +global using BossBoarFight = GClass45; +global using ScatteringData = GClass451; +global using CoverFinderAnalyzer = GClass453; +global using Data24 = GClass457; +global using DangerData = GClass458; +global using NextDecision = GClass459; +global using BoarSnEn = GClass46; +global using CombineBotPath = GClass467; +global using FBoarFght = GClass47; +global using Boarsntg = GClass48; +global using AbstractFollowerAIBase = GClass480; +global using AllFollowersPatrolInfo = GClass487; +global using BullyLayer = GClass49; +global using GroupDangerAreas = GClass490; +global using LastDamageData = GClass496; +global using GetLastSound = GClass499; +global using Killlogic = GClass50; +global using DoorOpenRequest = GClass509; +global using TryWarnPlayerRequest = GClass515; +global using GluharKilla = GClass52; +global using Data25 = AITaskManager.GClass520; +global using AimingStats = GClass538; +global using BossGlFight = GClass54; +global using GainSightStats = GClass543; +global using CutController = GClass548; +global using GluhAssKilla = GClass55; +global using GitVersion = GClass551; +global using TimerManager = GClass553; +global using CachedResponse = GClass555; +global using BackResponse = GClass558; +global using Type5 = GClass559.EConnectionType; +global using FlGlScout = GClass56; +global using ResponseJson = GClass562; +global using GClass564Status = GClass564.WsRequestStatus; +global using Debug1 = GClass57; +global using FollowPlayer = GClass58; +global using Khorovod = GClass59; +global using ObdPtrl = GClass60; +global using MineManager = GClass611; +global using EndInvoke = GClass627; +global using PlayerSpawned = GClass627.ActionId; +global using PlayerLogger = EFT.Player.GClass628; +global using BirdHold = GClass63; +global using PoolManagerLogger = PoolManager.GClass630; +global using InsuranceCompanyClassLogger = InsuranceCompanyClass.GClass635; +global using PtrlBirdEye = GClass64; +global using KnightFight = GClass65; +global using GPlayerFrame = GClass668; +global using BoarStationary = GClass67; +global using StateInfo = GClass672; +global using ExURequest = GClass68; +global using Stamina = GClass680; +global using IPlayersBridge = GClass681.IObserverToPlayerBridge; +global using GProne = GClass682.EPose; +global using Base1 = GClass682.EConsumptionTarget; +global using PmcStats = GClass694; +global using PatrolFollower = GClass70; +global using LoadFromAsset = GClass700; +global using Help1 = GClass71; +global using OriginalState = GClass712; +global using GroupForce = GClass72; +global using Fullmappatrol = GClass73; +global using GDistance = GClass738.CheckType; +global using Gifter = GClass74; +global using GlGoal = GClass75; +global using HoldNearBoss = GClass76; +global using GSide1 = GClass760.ESideTurn; +global using PeaceZrchPrtl = GClass77; +global using RavangeZryachiy = GClass78; +global using BackendConfigManagerConfig = GClass785; +global using GClass785Pools = GClass785.PoolsSettings; +global using GClass785Profiling = GClass785.ProfilingSettings; +global using GClass785CharacterController = GClass785.CharacterControllerSettings; +global using GClass785Physics = GClass785.PhysicsSettings; +global using GClass785Network = GClass785.NetworkConfig; +global using BorrowWeaponAudioQueue = GClass792; +global using DebugOcclusionRayInfo = GClass798; +global using PrstSummon = GClass80; +global using PriestPatrol = GClass81; +global using CreateConcaveBorder = GClass812; +global using HoldOrCover = GClass82; +global using Renderer1 = GClass829.Struct113; +global using Alignment = GClass830.HintAlignment; +global using Type6 = GClass841.LUTType; +global using DropPlacer = GClass843; +global using Quadrant = GClass849; +global using DrawInstance = GClass854; +global using FolKojEnemy = GClass86; +global using Face1 = GClass869; +global using KojaniyTarget = GClass87; +global using KsPartol = GClass88; +global using JobParameters = CullingManager.GClass883; +global using CullingObject1 = GClass886; +global using PositiveDefinite = GClass893.DefinitenessType; +global using Explorer = GClass895; +global using NewNode = GClass897; +global using LightPool = GClass904; +global using GClass905PerformanceRating = GClass905.PerfRating; +global using GMesh2 = DeferredDecals.DeferredDecalRenderer.GClass907; +global using KolontayFight = GClass91; +global using GClass919Action = GClass919.ActionState; +global using GClass919Pose = GClass919.PoseState; +global using GClass919Speed = GClass919.SpeedState; +global using KlnSolo = GClass92; +global using GGetInstance = GClass920; +global using SyncTime = GClass921.ESyncTime; +global using SecurityKln = GClass93; +global using GInvitation = GClass930; +global using RoomInformation = GClass933; +global using KolontayAP = GClass94; +global using Settings16 = GClass959; +global using GDisabled1 = GClass959.EItemQuickUseMode; +global using Automatic = GClass959.EAutoVaultingUseMode; +global using KlnForceAtk = GClass96; +global using Settings17 = GClass960; +global using KlnTrg = GClass97; +global using AbstractObstructionCalculator = GClass976; +global using Malfunction = GClass98; +global using Route1 = GClass986; +global using MarksmanEnemy = GClass99; +global using AudioMixerData = GClass993; +global using BakeInformation = GClass998; +global using IGame = GInterface100; +global using IGIHandler = GInterface105; +global using IGIHandler1 = GInterface106; +global using IGIHandler2 = GInterface107; +global using IGIHandler3 = GInterface108; +global using IGIHandler4 = GInterface109; +global using IGIHandler5 = GInterface110; +global using IGIHandler6 = GInterface111; +global using IGIHandler7 = GInterface112; +global using IGIHandler8 = GInterface113; +global using IGIHandler9 = GInterface114; +global using IGIHandler10 = GInterface115; +global using IGIHandler11 = GInterface116; +global using IGIHandler12 = GInterface117; +global using IGIHandler13 = GInterface118; +global using IGIHandler14 = GInterface119; +global using ILoot = GInterface12; +global using IFirearms = GInterface127; +global using IGIController4 = GInterface129; +global using ICollisionListener = GInterface137; +global using IGetDataSender = EFT.ClientPlayer.GInterface150; +global using IGetAxisUpdater = GInterface159; +global using ISender = GInterface167; +global using IPlayerBridge = BodyPartCollider.GInterface17; +global using IQteBehaviour = GInterface172; +global using IGIView = GInterface173; +global using IGIView1 = GInterface174; +global using IProfileCreator = GInterface18; +global using IMetricsCollector = GInterface186; +global using IExistingValue = GInterface188; +global using IGIView2 = GInterface193; +global using IGIView3 = GInterface194; +global using IGIView4 = GInterface195; +global using IPage = GInterface196; +global using IGIView5 = GInterface199; +global using IGIModel = GInterface205; +global using IMovesModel = GInterface210; +global using IGIModel1 = GInterface212; +global using IObstacleCalculatorModel = GInterface216; +global using IGrip = GInterface23; +global using IStaticLootSpawn = GInterface26; +global using IExplosiveItemTemplate = GInterface285; +global using IApplicable = GInterface306; +global using IReleaseListener = BetterSource.GInterface31; +global using IFavoriteComponent = GInterface335; +global using IStrategy = GInterface34; +global using IEventObject = GInterface343; +global using ITransportee = EFT.MovingPlatforms.MovingPlatform.GInterface349; +global using IBreakable = EFT.Interactive.WindowBreakerManager.GInterface355; +global using ISelectionHandler = EFT.UI.ItemSelectionCell.GInterface363; +global using IModdingScreen = GInterface365; +global using IProfileChangeHandler = EFT.UI.NotifierView.GInterface368; +global using IHealthObserver = GInterface373; +global using IBundleLoader = GInterface382; +global using IGIView6 = GInterface385; +global using IInvoke = GInterface387; +global using IGetAwaiter = GInterface388; +global using IWayTo = GInterface4; +global using IGIEmitter = GInterface40; +global using IInternalUpdateAxis = GInterface401; +global using IGIEmitter1 = GInterface41; +global using ITextureMaskHolder = GInterface45; +global using ISpeaker = GInterface55; +global using ISampleSetFactory = GInterface61; +global using IPatrolMove = GInterface7; +global using IGetConverter = GInterface70; +global using ICuller = GInterface72; +global using IGIGame = CommonAssets.Scripts.Game.EndByExitTrigerScenario.GInterface87; +global using IListener = GInterface88; +global using IGIListener = GInterface91; +global using ILogger1 = GInterface92; +global using IMetrics = GInterface97; +global using DataPair = GraphManager.GStruct1; +global using Settings18 = GStruct102; +global using Settings19 = GStruct103; +global using TransformSync = GStruct105; +global using Data26 = GStruct11; +global using DisconnectionReason = GStruct116; +global using AIData1 = GStruct12; +global using GPacket9 = GStruct128; +global using GPacket10 = GStruct129; +global using FlatItemsToTree = ItemFactory.GStruct134; +global using GOther19 = EFT.Player.GStruct135; +global using BotData = GStruct14; +global using Data28 = GStruct16; +global using PacketAuxiliaryData = GStruct161; +global using LightsStates = GStruct163; +global using ScopeStates = GStruct164; +global using FromSnapshot = GStruct166; +global using Data29 = GStruct17; +global using GOther25 = GStruct172; +global using PlayerEmitterCache = GStruct174; +global using GPrevFrame3 = GStruct175; +global using FiredShotInfo = GStruct179; +global using InfoStruct = GStruct18; +global using FireModePacket = GStruct180; +global using ToggleTacticalCombo = GStruct182; +global using LauncherRangeStatePacket = GStruct184; +global using ChangeSightsMode = GStruct185; +global using SetLeftStancePacket = GStruct188; +global using ReloadBarrelsPacket = GStruct191; +global using RollCylinderPacket = GStruct192; +global using CylinderMagStatusPacket = GStruct193; +global using ReloadWithAmmoPacket = GStruct194; +global using GStruct194ReloadWithAmmoStatus = GStruct194.EReloadWithAmmoStatus; +global using InteractionInfo = GStruct198; +global using PatrolTarget = GStruct20; +global using GFootprint1 = GStruct200; +global using Source4 = GStruct208; +global using Source5 = GStruct209; +global using RadioTransmitterPacket = GStruct213; +global using RealResistance = GStruct22; +global using GetSequenceSettings = EFT.AudioSequence.GStruct231; +global using GOther28 = EFT.Settings.Graphics.EftDisplay.GStruct235; +global using GGetPreset = GStruct237; +global using GOther30 = GStruct239; +global using Reference1 = GStruct242; +global using GPrice = TraderClass.GStruct243; +global using Data30 = GStruct247; +global using Data31 = GStruct250; +global using InterpolatedParams = GStruct253; +global using CalculateSphericalHarmonics = EFT.Weather.ToDController.GStruct254; +global using Branch = GStruct256; +global using Data32 = GStruct258; +global using DataModel = GStruct264; +global using Data33 = GStruct269; +global using Data34 = GStruct272; +global using ArenaObservedPlayerSpawnMessage = GStruct274; +global using GFromSnapshot = GStruct275; +global using CompletedInstance = GStruct279; +global using GNextModel1 = GStruct287; +global using DeathPacket = GStruct315; +global using ToEquipmentItemInfo = GStruct320; +global using ToInventorySyncPacket = GStruct321; +global using FlareShotInfo = GStruct322; +global using GrenadeThrowData = GStruct324; +global using HideWeaponPacket = GStruct325; +global using HitInfo = GStruct326; +global using OperationStatus1 = GStruct327; +global using LauncherReloadInfo = GStruct330; +global using PhraseCommandPacket = GStruct332; +global using PrevShot = GStruct333; +global using MedEffect2 = GStruct343; +global using Stimulator2 = GStruct344; +global using GStruct346SyncType = GStruct346.ESyncType; +global using AirdropDataPacket = GStruct37; +global using AirplaneDataPacket = GStruct38; +global using WatchBundleInfo = GStruct381; +global using SightStatus = EFT.CameraControl.OpticSight.GStruct382; +global using GetRelationByPosition = GStruct387; +global using DevelopDataPacket = GStruct39; +global using GItem25 = GStruct392; +global using GetMoneyTuple = GStruct396; +global using PriceData = InsuranceCompanyClass.GStruct402; +global using OffersData = GStruct403; +global using SOperationResult = GStruct411; +global using SetName = GStruct415; +global using GetBodyRenderer = GStruct54; +global using Data35 = CullingManager.GStruct61; +global using VisibilityData = CullingManager.GStruct62; +global using BakeParameters = GStruct76; +global using Destination = GStruct83; +global using Layer = GStruct9; +global using SpanBound = GStruct96; +global using MeasurementData = GStruct98; +global using GMovementState = Class1146; +global using PlayerVoipController = EFT.ClientPlayer.Class1402; +global using AbstractKeyCombinationState = GClass1895.Class1571; +global using GAbstractKeyCombinationState = GClass1895.Class1574; +global using KeyCombinationState1 = GClass1895.Class1579; +global using ContinuousIdlingState1 = GClass1896.Class1580; +global using ContinuousPressedState1 = GClass1896.Class1581; +global using KeyCombinationState2 = GClass1895.Class1582; +global using KeyCombinationState3 = GClass1895.Class1583; +global using KeyCombinationState4 = GClass1895.Class1584; +global using KeyCombinationState5 = GClass1895.Class1585; +global using KeyCombinationState6 = GClass1895.Class1586; +global using AbstractConnectionHandler = Class1719; +global using GSlot2 = EFT.UI.ItemSpecificationPanel.Class1980; +global using PooledObjectPolicy1 = EFT.AssetsManager.AssetPoolObject.Class2348; +global using BotRequest1 = Class243; +global using BotRequest2 = Class244; +global using List1 = EFT.UI.Health.DamagePanel.Class2915; +global using GInventoryError = GClass2092.Class3136; +global using GInventoryError1 = GClass2092.Class3137; +global using GMotionState = Class3199; +global using GMotionState1 = Class3200; +global using GMotionState2 = Class3201; +global using GMotionState3 = Class3202; +global using MessageBase1 = EFT.AbstractGameSession.Class875; +global using YieldInstruction = GClass1002; +global using GYieldInstruction = GClass1008; +global using GYieldInstruction1 = GClass1009; +global using JsonConverter1 = GClass1234; +global using GIKSolverTrigonometric = GClass1271; +global using GBotSpawner = GClass1472; +global using GBotSpawner1 = GClass1473; +global using PlayerAnimatorEvents = GClass1677; +global using VaultingSoundsEvents = GClass1679; +global using GVaultingSoundsEvents = GClass1680; +global using GVaultingSoundsEvents1 = GClass1681; +global using GMovementContext = GClass1685; +global using GMovementState1 = GClass1686; +global using GMovementState2 = GClass1688; +global using GMovementState4 = GClass1692; +global using GMovementState5 = GClass1701; +global using GMovementState6 = GClass1703; +global using GMovementState7 = GClass1705; +global using GMovementState8 = GClass1709; +global using GMovementState9 = GClass1710; +global using GMovementState10 = GClass1711; +global using GMovementState11 = GClass1712; +global using AbstractMovementState = GClass1713; +global using AbstractBaseMovementState = GClass1716; +global using StatisticsManager = GClass1788; +global using AStatisticsManagerForPlayer = GClass1790; +global using GRelatedData = GClass1933; +global using AsyncCommand = GClass1958; +global using GAsyncCommand = GClass1959; +global using VaultingRestrictions = GClass2154; +global using GVaultingRestrictions = GClass2155; +global using VaultingComponentDebug = GClass2157; +global using VaultingGameplayRestrictions = GClass2158; +global using AutomaticVaultingModelDebug = GClass2183; +global using GridSettingsModelDebug = GClass2184; +global using VaultingRestrictionsModelDebug = GClass2185; +global using SurfaceApproximatorModelDebug = GClass2186; +global using GridPointsModelDebug = GClass2187; +global using GridRootMoverModelDebug = GClass2188; +global using ObstacleCalculatorModelDebug = GClass2189; +global using VaultingStateModelDebug = GClass2192; +global using VaultingModelDebug = GClass2194; +global using ObstacleCollisionFacade = GClass2205; +global using InventoryProfileSkillInfo = GClass2380.GClass2381; +global using GHealthValue = GClass2467; +global using NetworkTransport1 = GClass2468; +global using ReadonlyItemComponent1 = GClass2496; +global using GItemTemplate = GClass2521; +global using BaseBrain1 = GClass253; +global using GItemTemplate1 = GClass2532; +global using BaseBrain2 = GClass254; +global using GModTemplate = GClass2545; +global using GModTemplate1 = GClass2546; +global using BaseBrain3 = GClass255; +global using BaseBrain4 = GClass256; +global using GModTemplate2 = GClass2569; +global using BaseBrain5 = GClass257; +global using GModTemplate3 = GClass2578; +global using BaseBrain6 = GClass259; +global using GWeaponTemplate = GClass2592; +global using GWeaponTemplate1 = GClass2593; +global using GWeaponTemplate2 = GClass2594; +global using GWeaponTemplate3 = GClass2595; +global using GWeaponTemplate4 = GClass2596; +global using GWeaponTemplate5 = GClass2597; +global using GWeaponTemplate6 = GClass2598; +global using GWeaponTemplate7 = GClass2599; +global using BaseBrain7 = GClass260; +global using GWeaponTemplate8 = GClass2600; +global using GWeaponTemplate9 = GClass2601; +global using GWeaponTemplate10 = GClass2602; +global using GItemTemplate3 = GClass2606; +global using GItemTemplate4 = GClass2609; +global using BaseBrain8 = GClass261; +global using GItemTemplate5 = GClass2611; +global using GItemTemplate6 = GClass2612; +global using GItemTemplate7 = GClass2619; +global using BaseBrain9 = GClass262; +global using GItemTemplate8 = GClass2624; +global using GItemTemplate9 = GClass2626; +global using GItemTemplate10 = GClass2628; +global using BaseBrain10 = GClass263; +global using AbstractBaseBrain = GClass264; +global using BaseBrain11 = GClass267; +global using BaseBrain12 = GClass268; +global using GWeapon1 = GClass2689; +global using BaseBrain13 = GClass269; +global using GWeapon2 = GClass2692; +global using GWeapon3 = GClass2693; +global using GWeapon4 = GClass2694; +global using GWeapon5 = GClass2695; +global using GWeapon6 = GClass2696; +global using GWeapon7 = GClass2697; +global using GWeapon8 = GClass2698; +global using GWeapon9 = GClass2699; +global using BaseBrain14 = GClass270; +global using GWeapon10 = GClass2700; +global using BaseBrain15 = GClass271; +global using GItem27 = GClass2715; +global using BaseBrain16 = GClass272; +global using GItem29 = GClass2720; +global using GItem31 = GClass2724; +global using BaseBrain17 = GClass273; +global using GItem33 = GClass2731; +global using GItem34 = GClass2738; +global using BaseBrain18 = GClass274; +global using GItem35 = GClass2740; +global using GAbstractBaseBrain = GClass275; +global using BaseBrain19 = GClass280; +global using BaseBrain20 = GClass281; +global using BaseBrain21 = GClass282; +global using BaseBrain22 = GClass283; +global using BaseBrain23 = GClass284; +global using BaseBrain24 = GClass285; +global using BaseBrain25 = GClass286; +global using BaseBrain26 = GClass287; +global using BaseBrain27 = GClass288; +global using BaseBrain28 = GClass289; +global using GBaseSyncEvent = GClass2890; +global using BaseBrain29 = GClass290; +global using GAbstractBaseBrain1 = GClass291; +global using GExfiltrationRequirement = GClass2953; +global using GExfiltrationRequirement1 = GClass2954; +global using GExfiltrationRequirement2 = GClass2955; +global using GExfiltrationRequirement3 = GClass2956; +global using GExfiltrationRequirement4 = GClass2957; +global using GExfiltrationRequirement5 = GClass2958; +global using GExfiltrationRequirement6 = GClass2959; +global using KeyInteractionResult = GClass2964; +global using CustomYieldInstruction = GClass3083; +global using GCustomYieldInstruction = GClass3084; +global using CoverSearchData1 = GClass318; +global using ReadOnlyDictionary1 = GClass3201; +global using GError3 = EFT.GameWorld.GClass3265; +global using GError4 = EFT.GameWorld.GClass3266; +global using GError5 = EFT.GameWorld.GClass3267; +global using GError6 = EFT.GameWorld.GClass3268; +global using GError7 = EFT.GameWorld.GClass3269; +global using AICorePointLink = GClass327; +global using GError8 = EFT.GameWorld.GClass3270; +global using GError9 = EFT.GameWorld.GClass3271; +global using GError10 = EFT.GameWorld.GClass3272; +global using GError11 = EFT.GameWorld.GClass3273; +global using GError12 = EFT.GameWorld.GClass3274; +global using GError13 = EFT.GameWorld.GClass3275; +global using GError14 = ItemFactory.GClass3276; +global using GError15 = ItemFactory.GClass3277; +global using GError16 = EFT.Player.GClass3278; +global using GError17 = EFT.Player.GClass3280; +global using GError18 = EFT.Player.GClass3281; +global using GError19 = EFT.Player.GClass3282; +global using GError20 = EFT.NetworkPlayer.GClass3283; +global using GInventoryError2 = EFT.InventoryLogic.LockableComponent.GClass3285; + +global using GInventoryError9 = GClass3292; +global using GInventoryError10 = GClass3293; +global using GInventoryError11 = GClass3294; +global using GInventoryError12 = GClass3295; +global using GInventoryError13 = GClass3296; +global using GInventoryError14 = GClass3297; +global using GInventoryError15 = GClass3298; +global using GInventoryError16 = GClass3299; +global using GInventoryError17 = GClass3300; +global using GInventoryError18 = GClass3301; +global using GInventoryError19 = GClass3302; +global using GInventoryError20 = GClass3303; +global using GInventoryError21 = GClass3304; +global using GInventoryError22 = GClass3305; +global using GInventoryError23 = GClass3306; +global using GInventoryError24 = EFT.InventoryLogic.Slot.GClass3307; +global using GInventoryError25 = EFT.InventoryLogic.Slot.GClass3308; +global using GInventoryError26 = EFT.InventoryLogic.Slot.GClass3309; +global using GInventoryError27 = EFT.InventoryLogic.Slot.GClass3310; +global using GInventoryError28 = EFT.InventoryLogic.Slot.GClass3311; +global using GInventoryError29 = EFT.InventoryLogic.Slot.GClass3312; +global using GInventoryError30 = EFT.InventoryLogic.Slot.GClass3313; +global using GInventoryError31 = EFT.InventoryLogic.Slot.GClass3314; +global using GInventoryError32 = EFT.InventoryLogic.Slot.GClass3315; +global using GInventoryError33 = EFT.InventoryLogic.Slot.GClass3316; +global using GInventoryError34 = EFT.InventoryLogic.Slot.GClass3317; +global using GInventoryError35 = EFT.InventoryLogic.Slot.GClass3318; +global using GInventoryError36 = EFT.InventoryLogic.Slot.GClass3319; +global using GInventoryError37 = GClass3320; +global using GInventoryError38 = GClass3321; +global using GInventoryError39 = GClass3322; +global using GInventoryError40 = GClass3323; +global using GInventoryError41 = GClass3324; +global using GInventoryError42 = MagazineClass.GClass3325; +global using GInventoryError43 = MagazineClass.GClass3326; +global using GInventoryError44 = MagazineClass.GClass3327; +global using GError21 = GClass3345; +global using GError22 = GClass3346; +global using GError23 = GClass3347; +global using BotController = GClass338; +global using ABossLogic1 = GClass363; +global using ABossLogic2 = GClass364; +global using ABossLogic3 = GClass365; +global using ABossLogic5 = GClass368; +global using ABossLogic7 = GClass372; +global using ABossLogic9 = GClass374; +global using ABossLogic10 = GClass378; +global using ABossLogic11 = GClass379; +global using ABossLogic12 = GClass380; +global using ABossLogic13 = GClass381; +global using BossWarnData1 = GClass383; +global using BotFollower1 = GClass384; +global using BotFollower2 = GClass385; +global using BotReload2 = GClass392; +global using BotReload3 = GClass393; +global using BotReload4 = GClass394; +global using BotReload5 = GClass395; +global using BotWeaponSelector1 = GClass397; +global using BotSearchData1 = GClass399; +global using BotSearchData2 = GClass400; +global using BotEnemyChooser1 = GClass401; +global using BotEnemyChooser2 = GClass402; +global using BotEnemyChooser3 = GClass403; +global using BotEnemyChooser4 = GClass404; +global using BotEnemyChooser5 = GClass405; +global using BotEnemyChooser6 = GClass406; +global using BotEnemyChooser7 = GClass407; +global using BotEnemyChooser8 = GClass408; +global using BotMover1 = GClass418; +global using BotMover2 = GClass419; +global using BotMover3 = GClass420; +global using AbstractSuppressStationary1 = GClass438; +global using AbstractSuppressStationary2 = GClass439; +global using AbstractSuppressStationary3 = GClass440; +global using BotSubTactic1 = GClass442; +global using BotSubTactic2 = GClass443; +global using BotSubTactic3 = GClass444; +global using BotSubTactic4 = GClass445; +global using BotSubTactic5 = GClass446; +global using BotSubTactic6 = GClass447; +global using BotRandomPlanItemDropper1 = GClass449; +global using BotAiming = GClass452; +global using BotSearchPoint1 = GClass461; +global using EnemyInfo2 = GClass462; +global using GPositionPoint1 = GClass470; +global using GPositionPoint2 = GClass471; +global using PatrolPointChooserBasic1 = GClass472; +global using PatrolPointChooserBasic2 = GClass474; +global using PatrolPointChooserBasic3 = GClass475; +global using PatrolPointChooserBasic4 = GClass476; +global using PatrolPointChooserBasic5 = GClass477; +global using PatrolPointChooserBasic6 = GClass478; +global using PatrolPointChooserBasic7 = GClass479; +global using BotRequest3 = GClass505; +global using BotRequest4 = GClass506; +global using BotRequest5 = GClass507; +global using BotRequest6 = GClass508; +global using BotRequest8 = GClass510; +global using AbstractBotRequest = GClass511; +global using GBotAiming = GClass518; +global using GGetProfileData = GClass595; +global using GGetProfileData1 = GClass596; +global using ObjectInHandsAnimator1 = GClass662; +global using WebClient1 = GClass750; +global using ABSTweenPlugin = GClass774; +global using Proxy4Interface1 = GClass926; +global using GChatMember = GClass927; +global using ChatsSession = GClass928; +global using GChatRoomMember = GClass932; +global using IEventSystemHandler1 = GInterface164; +global using IPositionPoint1 = GInterface2; +global using IEffect2 = GInterface229; +global using IEffect3 = GInterface230; +global using IEffect4 = GInterface241; +global using IEffect5 = GInterface242; +global using IEffect6 = GInterface243; +global using IEffect7 = GInterface244; +global using IEffect8 = GInterface245; +global using IEffect9 = GInterface246; +global using IEffect10 = GInterface247; +global using IEffect11 = GInterface248; +global using IEffect12 = GInterface249; +global using IEffect13 = GInterface250; +global using IEffect14 = GInterface251; +global using IEffect15 = GInterface252; +global using IEffect16 = GInterface253; +global using IEffect17 = GInterface254; +global using IEffect18 = GInterface255; +global using IEffect19 = GInterface256; +global using IEffect20 = GInterface257; +global using IEffect21 = GInterface258; +global using IEffect22 = GInterface259; +global using IEffect23 = GInterface260; +global using IEffect24 = GInterface261; +global using IEffect25 = GInterface262; +global using IEffect26 = GInterface263; +global using IEffect27 = GInterface264; +global using IEffect28 = GInterface265; +global using IEffect29 = GInterface266; +global using IEffect30 = GInterface267; +global using IEffect31 = GInterface268; +global using IEffect32 = GInterface269; +global using IEffect33 = GInterface270; +global using IEffect34 = GInterface271; +global using IEffect35 = GInterface272; +global using IEffect36 = GInterface273; +global using IEffect37 = GInterface274; +global using IEffect38 = GInterface275; +global using IEffect39 = GInterface276; +global using IEffect40 = GInterface277; +global using IEffect41 = GInterface279; +global using IEffect42 = GInterface280; +global using IEffect43 = GInterface281; +global using IItemComponent1 = GInterface311; +global using IStateBehaviour = GInterface85; +global using JobParallelFor = GStruct28; +global using GJobParallelFor = GStruct282; +global using GJobParallelFor1 = GStruct284; +global using GJobParallelFor2 = GStruct289; +global using AbstractFirearmActioner = EFT.Player.FirearmController.GClass1584; +global using BackendServerConnection = Class262; +global using FilterCustomizationClass = GClass1445; +global using FilterCustomizationClass1 = GClass1446; +global using StatisticsManagerForPlayer = GClass1789; +global using BackendConfigManager = GClass549; +global using LocalizedNotification = GClass2040; +global using GridContainer = GClass2502; +global using BossWaveManager = GClass579; +global using SpawnSystemFactory = GClass2929; +global using ResourceBundleConstants = GClass1388; +global using TokenStarter = GClass21; +global using AIActionNodeAssigner = GClass460; +global using BotCreator = GClass813; +global using OperationToDescriptorHelpers = GClass1632; +global using AbstractInventoryOperation2 = GClass2838; +global using MoveInternalOperation = GClass2839; +global using AbstractAmmoManipulationOperation = GClass2861; +global using BSGNetworkWriter = GClass1081; +global using BSGNetworkWriterExtensions = GClass1084; +global using BSGNetworkReader = GClass1076; +global using BSGNetworkReaderExtensions = GClass1079; +global using BSGNetworkConversionHelpers = GClass1089; +global using BSGDirectionalHelpers = GClass1657; +global using QuestControllerSIT = GClass3207; +global using TradingBackend = Class263; +global using InventorySerializationHelpers = GClass1524; +global using ArmorInfo = GClass2311; +global using BSGMemoryGC = GClass772; +global using AHealthController = GClass2416; +global using PlayerLoopSystemHelpers = GClass567; +global using BundleLoaderProgressStruct = GStruct118; +global using BundleLoaderProgress = GClass3262; +global using ItemsCount = GStruct369; +global using BSGUnityHelper = GClass771; diff --git a/Source/Memory/GCHelpers.cs b/Source/Memory/GCHelpers.cs index 6b02b53a7..88255676d 100644 --- a/Source/Memory/GCHelpers.cs +++ b/Source/Memory/GCHelpers.cs @@ -24,110 +24,14 @@ static GCHelpers() Logger = BepInEx.Logging.Logger.CreateLogSource("GCHelpers"); } - public static void EmptyWorkingSet() + public static void RunBSGGarbageCollection() { - EmptyWorkingSetCall(Process.GetCurrentProcess().Handle); - } - - public static bool Emptying = false; - - public static void EnableGC() - { - if (GarbageCollector.GCMode == GarbageCollector.Mode.Disabled) - { - Logger.LogDebug($"EnableGC():Enabled GC"); - GarbageCollector.GCMode = GarbageCollector.Mode.Enabled; - } - Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.Normal; - } - - public static void DisableGC(bool forceCollect = false) - { - - if (GarbageCollector.GCMode == GarbageCollector.Mode.Enabled) - { - Collect(forceCollect); - Logger.LogDebug($"DisableGC():Disabled GC"); - GarbageCollector.GCMode = GarbageCollector.Mode.Disabled; - } - } - - public static void ClearGarbage(bool emptyTheSet = false, bool unloadAssets = true) - { - Logger.LogDebug($"ClearGarbage()"); - - if (!emptyTheSet) - { - EnableGC(); - Collect(force: true); - } - - if (Emptying) - return; - - if (unloadAssets) - Resources.UnloadUnusedAssets(); - - if (emptyTheSet) - { - Emptying = true; - RunHeapPreAllocation(); - Collect(force: true); - EmptyWorkingSet(); - } - Emptying = false; - DisableGC(); - } - - public static void RunHeapPreAllocation() - { - Stopwatch stopwatch = Stopwatch.StartNew(); - int num = Math.Max(0, 200); - if (num > 0) - { - object[] array = new object[1024 * num]; - for (int i = 0; i < array.Length; i++) - { - array[i] = new byte[1024]; - } - array = null; - stopwatch.Stop(); - } - } - - public static void Collect(bool force = false) - { - Logger.LogDebug($"Collect({force})"); - - Collect(2, GCCollectionMode.Optimized, isBlocking: true, compacting: false, force); - } - - public static float GetTotalAllocatedMemoryGB() - { - return (float)GC.GetTotalMemory(forceFullCollection: true) / 1024f / 1024f; - } - - public static float PreviousTime { get; set; } - - public static void Collect(int generation, GCCollectionMode gcMode, bool isBlocking, bool compacting, bool force) - { - - if (!force && Time.time < PreviousTime + 600f) - { - return; - } - if (force) - { - float totalAllocatedMemoryGB = GetTotalAllocatedMemoryGB(); - GC.Collect(); - //if (Settings.AggressiveGC) - { - GC.WaitForPendingFinalizers(); - GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; - GC.Collect(generation, gcMode, isBlocking, compacting); - } - } - PreviousTime = Time.time; + Logger.LogDebug($"{nameof(RunBSGGarbageCollection)}"); + BSGMemoryGC.RunHeapPreAllocation(); + BSGMemoryGC.Collect(force: true); + BSGMemoryGC.EmptyWorkingSet(); + BSGMemoryGC.GCEnabled = true; + Resources.UnloadUnusedAssets(); } } diff --git a/Source/Networking/AkiBackendCommunication.cs b/Source/Networking/AkiBackendCommunication.cs index 91c0ca355..fc2faf757 100644 --- a/Source/Networking/AkiBackendCommunication.cs +++ b/Source/Networking/AkiBackendCommunication.cs @@ -1,12 +1,11 @@ -using BepInEx.Logging; +#nullable enable + +using BepInEx.Logging; using Comfort.Common; using EFT; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using StayInTarkov.Configuration; using StayInTarkov.Coop.Components.CoopGameComponents; -using StayInTarkov.Coop.Matchmaker; -using StayInTarkov.Coop.NetworkPacket; using StayInTarkov.Coop.SITGameModes; using StayInTarkov.ThirdParty; using System; @@ -22,13 +21,12 @@ using System.Threading; using System.Threading.Tasks; using UnityEngine; -using UnityEngine.XR; namespace StayInTarkov.Networking { public class AkiBackendCommunication : IDisposable { - public const int DEFAULT_TIMEOUT_MS = 9999; + public const int DEFAULT_TIMEOUT_MS = 4444; public const int DEFAULT_TIMEOUT_LONG_MS = 9999; public const string PACKET_TAG_METHOD = "m"; public const string PACKET_TAG_SERVERID = "serverId"; @@ -57,7 +55,7 @@ public string RemoteEndPoint m_RemoteEndPoint = StayInTarkovHelperConstants.GetBackendUrl(); // Remove ending slash on URI for SIT.Manager.Avalonia - if(m_RemoteEndPoint.EndsWith("/")) + if (m_RemoteEndPoint.EndsWith("/")) m_RemoteEndPoint = m_RemoteEndPoint.Substring(0, m_RemoteEndPoint.Length - 1); return m_RemoteEndPoint; @@ -66,10 +64,12 @@ public string RemoteEndPoint set { m_RemoteEndPoint = value; } } - //public bool isUnity; - private Dictionary m_RequestHeaders { get; set; } + private Dictionary? m_RequestHeaders = null; + + + #region Instance - private static AkiBackendCommunication m_Instance { get; set; } + private static AkiBackendCommunication? m_Instance; public static AkiBackendCommunication Instance { get @@ -81,10 +81,19 @@ public static AkiBackendCommunication Instance } } - public HttpClient HttpClient { get; set; } + #endregion + + #region HttpClient + + protected HttpClient HttpClient { get; set; } + protected string Cookie; + + #endregion protected ManualLogSource Logger; + #region WebSocket + public WebSocketSharp.WebSocket WebSocket { get; private set; } public long BytesSent = 0; @@ -92,9 +101,9 @@ public static AkiBackendCommunication Instance public ushort Ping = 0; public ConcurrentQueue ServerPingSmooth { get; } = new(); - public static int PING_LIMIT_HIGH { get; } = 125; - public static int PING_LIMIT_MID { get; } = 100; - public static bool IsLocal; + public static bool IsLocal { get; set; } + + #endregion protected AkiBackendCommunication(ManualLogSource logger = null) @@ -107,7 +116,7 @@ protected AkiBackendCommunication(ManualLogSource logger = null) if (logger != null) Logger = logger; else - Logger = BepInEx.Logging.Logger.CreateLogSource("Request"); + Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(AkiBackendCommunication)); if (string.IsNullOrEmpty(RemoteEndPoint)) RemoteEndPoint = StayInTarkovHelperConstants.GetBackendUrl(); @@ -116,26 +125,33 @@ protected AkiBackendCommunication(ManualLogSource logger = null) || RemoteEndPoint.Contains("localhost"); GetHeaders(); - ConnectToAkiBackend(); - PeriodicallySendPing(); - //PeriodicallySendPooledData(); - SITGameServerClientDataProcessing.OnLatencyUpdated += OnLatencyUpdated; + var processor = StayInTarkovPlugin.Instance.GetOrAddComponent(); + Singleton.Create(processor); + Comfort.Common.Singleton.Instance.OnLatencyUpdated += OnLatencyUpdated; - HttpClient = new HttpClient(); - foreach (var item in GetHeaders()) + // Setup Cookie Header + Cookie = $"PHPSESSID={GetHeaders()["Cookie"]}"; + var handler = new HttpClientHandler { + UseCookies = false + }; + // Client single HttpClient + HttpClient = new HttpClient(handler); + foreach (var item in GetHeaders()) HttpClient.DefaultRequestHeaders.Add(item.Key, item.Value); - } + HttpClient.MaxResponseContentBufferSize = long.MaxValue; - HttpClient.Timeout = new TimeSpan(0, 0, 0, 0, 1000); + + ConnectToAkiBackend(); + PeriodicallySendPing(); HighPingMode = PluginConfigSettings.Instance.CoopSettings.ForceHighPingMode; } private void ConnectToAkiBackend() { - PooledJsonToPostToUrl.Add(new KeyValuePair("/coop/connect", "{}")); + this.PostJsonBLOCKING("/coop/connect", "{}"); } private Profile MyProfile { get; set; } @@ -166,7 +182,7 @@ public void WebSocketCreate(Profile profile) WebSocket.WaitTime = TimeSpan.FromMinutes(1); WebSocket.EmitOnPing = true; WebSocket.Connect(); - WebSocket.Send("CONNECTED FROM SIT COOP"); + // --- // Start up after initial Send WebSocket.OnError += WebSocket_OnError; @@ -243,7 +259,15 @@ private void WebSocket_OnMessage(object sender, WebSocketSharp.MessageEventArgs Interlocked.Add(ref BytesReceived, e.RawData.Length); GC.AddMemoryPressure(e.RawData.Length); - SITGameServerClientDataProcessing.ProcessPacketBytes(e.RawData, e.Data); + var d = e.RawData; + if (d.Length >= 3 && d[0] != '{' && !(d[0] == 'S' && d[1] == 'I' && d[2] == 'T')) + { + Singleton.Instance.ProcessFlatBuffer(d); + } + else + { + Singleton.Instance.ProcessPacketBytes(e.RawData); + } GC.RemoveMemoryPressure(e.RawData.Length); } @@ -276,7 +300,7 @@ public static AkiBackendCommunication GetRequestInstance(bool createInstance = f // WebSocket.Send(serializedData); //} - private HashSet _previousPooledData = new HashSet(); + private HashSet _previousPooledData = new(); //public void SendDataToPool(byte[] serializedData) //{ @@ -471,7 +495,8 @@ private void PeriodicallySendPing() WebSocket.Send(Encoding.UTF8.GetBytes(packet.ToJson())); packet = null; - } catch (Exception ex) + } + catch (Exception ex) { Logger.LogError($"Periodic ping caught: {ex.GetType()} {ex.Message}"); } @@ -510,410 +535,243 @@ private Dictionary GetHeaders() return m_RequestHeaders; } + + /// /// Send request to the server and get Stream of data back /// - /// String url endpoint example: /start + /// String url endpoint example: /start /// POST or GET /// string json data /// Should use compression gzip? /// Stream or null - private MemoryStream SendAndReceive(string url, string method = "GET", string data = null, bool compress = true, int timeout = 9999, bool debug = false) + private async Task AsyncRequestFromPath(string path, CancellationTokenSource cts, string method = "GET", string? data = null, int timeout = 9999, bool debug = false) { - // Force to DEBUG mode if not Compressing. - debug = debug || !compress; - - HttpClient.Timeout = TimeSpan.FromMilliseconds(timeout); - - - method = method.ToUpper(); - - var fullUri = url; - if (!Uri.IsWellFormedUriString(fullUri, UriKind.Absolute)) - fullUri = RemoteEndPoint + fullUri; - - if (method == "GET") - { - var ms = new MemoryStream(); - var stream = HttpClient.GetStreamAsync(fullUri); - stream.Result.CopyTo(ms); - return ms; - } - else if (method == "POST" || method == "PUT") + if (!Uri.IsWellFormedUriString(path, UriKind.Absolute)) { - var uri = new Uri(fullUri); - return SendAndReceivePostOld(uri, method, data, compress, timeout, debug); + path = RemoteEndPoint + path; } - throw new ArgumentException($"Unknown method {method}"); + return await AsyncRequest(new Uri(path), cts, method, data, timeout, debug); } - // - /// Send request to the server and get Stream of data back - /// - /// String url endpoint example: /start - /// POST or GET - /// string json data - /// Should use compression gzip? - /// Stream or null - private async Task SendAndReceiveAsync(string url, string method = "GET", string data = null, bool compress = true, int timeout = 9999, bool debug = false) + private async Task AsyncRequest(Uri uri, CancellationTokenSource cts, string method = "GET", string? data = null, int timeout = 9999, bool debug = false) { - // Force to DEBUG mode if not Compressing. - debug = debug || !compress; - - HttpClient.Timeout = TimeSpan.FromMilliseconds(timeout); - - - method = method.ToUpper(); + var compress = true; + var httpClient = this.HttpClient; - var fullUri = url; - if (!Uri.IsWellFormedUriString(fullUri, UriKind.Absolute)) - fullUri = RemoteEndPoint + fullUri; - - if (method == "GET") - { - var ms = new MemoryStream(); - var stream = await HttpClient.GetStreamAsync(fullUri); - stream.CopyTo(ms); - return ms; - } - else if (method == "POST" || method == "PUT") + // Reset AcceptEncoding + httpClient.DefaultRequestHeaders.AcceptEncoding.Clear(); + if (!debug && method == "POST") { - var uri = new Uri(fullUri); - return await SendAndReceivePostAsync(uri, method, data, compress, timeout, debug); + httpClient.DefaultRequestHeaders.AcceptEncoding.TryParseAdd("deflate"); } + // Reset debug + if (httpClient.DefaultRequestHeaders.Contains("debug")) + httpClient.DefaultRequestHeaders.Remove("debug"); - throw new ArgumentException($"Unknown method {method}"); - } - /// - /// Send request to the server and get Stream of data back by post - /// - /// - /// - /// - /// - /// - /// - /// - MemoryStream SendAndReceivePostOld(Uri uri, string method = "GET", string data = null, bool compress = true, int timeout = 9999, bool debug = false) - { - using (HttpClientHandler handler = new HttpClientHandler()) + HttpContent? byteContent = null; + if (method.Equals("POST", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(data)) { - using (HttpClient httpClient = new HttpClient(handler)) + // If debug. We do not compress. + if (debug) { - handler.UseCookies = true; - handler.CookieContainer = new CookieContainer(); - httpClient.Timeout = TimeSpan.FromMilliseconds(timeout); - Uri baseAddress = new Uri(RemoteEndPoint); - foreach (var item in GetHeaders()) - { - if (item.Key == "Cookie") - { - string[] pairs = item.Value.Split(';'); - var keyValuePairs = pairs - .Select(p => p.Split(new[] { '=' }, 2)) - .Where(kvp => kvp.Length == 2) - .ToDictionary(kvp => kvp[0], kvp => kvp[1]); - foreach (var kvp in keyValuePairs) - { - handler.CookieContainer.Add(baseAddress, new Cookie(kvp.Key, kvp.Value)); - } - } - else - { - httpClient.DefaultRequestHeaders.TryAddWithoutValidation(item.Key, item.Value); - } - - } - if (!debug && method == "POST") - { - httpClient.DefaultRequestHeaders.AcceptEncoding.TryParseAdd("deflate"); - } - - HttpContent byteContent = null; - if (method.Equals("POST", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(data)) - { - if (debug) - { - compress = false; - httpClient.DefaultRequestHeaders.Add("debug", "1"); - } - var inputDataBytes = Encoding.UTF8.GetBytes(data); - byte[] bytes = compress ? Zlib.Compress(inputDataBytes) : inputDataBytes; - byteContent = new ByteArrayContent(bytes); - byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - if (compress) - { - byteContent.Headers.ContentEncoding.Add("deflate"); - } - } - - HttpResponseMessage response; - if (byteContent != null) - { - response = httpClient.PostAsync(uri, byteContent).Result; - } - else - { - response = method.Equals("POST", StringComparison.OrdinalIgnoreCase) - ? httpClient.PostAsync(uri, null).Result - : httpClient.GetAsync(uri).Result; - } - - var ms = new MemoryStream(); - if (response.IsSuccessStatusCode) - { - Stream responseStream = response.Content.ReadAsStreamAsync().Result; - responseStream.CopyTo(ms); - responseStream.Dispose(); - } - else - { - StayInTarkovHelperConstants.Logger.LogError($"Unable to send api request to server.Status code" + response.StatusCode); - } - - return ms; + compress = false; + httpClient.DefaultRequestHeaders.Add("debug", "1"); } + var inputDataBytes = Encoding.UTF8.GetBytes(data); + var bytes = compress ? Zlib.Compress(inputDataBytes, ZlibCompression.Normal) : inputDataBytes; + byteContent = new ByteArrayContent(bytes); + byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + if (compress) + { + byteContent.Headers.ContentEncoding.Add("deflate"); + } } - } - async Task SendAndReceivePostAsync(Uri uri, string method = "GET", string data = null, bool compress = true, int timeout = 9999, bool debug = false) - { - using (HttpClientHandler handler = new HttpClientHandler()) - { - using (HttpClient httpClient = new HttpClient(handler)) - { - handler.UseCookies = true; - handler.CookieContainer = new CookieContainer(); - httpClient.Timeout = TimeSpan.FromMilliseconds(timeout); - Uri baseAddress = new Uri(RemoteEndPoint); - foreach (var item in GetHeaders()) - { - if (item.Key == "Cookie") - { - string[] pairs = item.Value.Split(';'); - var keyValuePairs = pairs - .Select(p => p.Split(new[] { '=' }, 2)) - .Where(kvp => kvp.Length == 2) - .ToDictionary(kvp => kvp[0], kvp => kvp[1]); - foreach (var kvp in keyValuePairs) - { - handler.CookieContainer.Add(baseAddress, new Cookie(kvp.Key, kvp.Value)); - } - } - else - { - httpClient.DefaultRequestHeaders.TryAddWithoutValidation(item.Key, item.Value); - } +#if DEBUG + var stopwatch = new Stopwatch(); + stopwatch.Start(); + Logger.LogDebug($"{nameof(AsyncRequest)} Send to {uri}"); +#endif - } - if (!debug && method == "POST") - { - httpClient.DefaultRequestHeaders.AcceptEncoding.TryParseAdd("deflate"); - } + HttpResponseMessage response; + if (method.Equals("POST", StringComparison.OrdinalIgnoreCase)) + { + response = await httpClient.PostAsync(uri, byteContent, cts.Token); + } + else + { + response = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cts.Token); + } - HttpContent byteContent = null; - if (method.Equals("POST", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(data)) - { - if (debug) - { - compress = false; - httpClient.DefaultRequestHeaders.Add("debug", "1"); - } - var inputDataBytes = Encoding.UTF8.GetBytes(data); - byte[] bytes = compress ? Zlib.Compress(inputDataBytes) : inputDataBytes; - byteContent = new ByteArrayContent(bytes); - byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - if (compress) - { - byteContent.Headers.ContentEncoding.Add("deflate"); - } - } + if (response.IsSuccessStatusCode) + { + using MemoryStream ms = new(); + using var stream = await response.Content.ReadAsStreamAsync(); + //var bytes = await response.Content.ReadAsByteArrayAsync(); + await stream.CopyToAsync(ms); + var bytes = ms.ToArray(); - HttpResponseMessage response; - if (byteContent != null) - { - response = await httpClient.PostAsync(uri, byteContent); - } - else - { - response = method.Equals("POST", StringComparison.OrdinalIgnoreCase) - ? await httpClient.PostAsync(uri, null) - : await httpClient.GetAsync(uri); - } + if (Zlib.IsCompressed(bytes)) + bytes = Zlib.Decompress(bytes); - var ms = new MemoryStream(); - if (response.IsSuccessStatusCode) - { - Stream responseStream = await response.Content.ReadAsStreamAsync(); - responseStream.CopyTo(ms); - responseStream.Dispose(); - } - else - { - StayInTarkovHelperConstants.Logger.LogError($"Unable to send api request to server.Status code" + response.StatusCode); - } + if (bytes.IsNullOrEmpty()) + bytes = Encoding.UTF8.GetBytes(response.StatusCode.ToString()); - return ms; - } +#if DEBUG + stopwatch.Stop(); + Logger.LogDebug($"{nameof(AsyncRequest)} Received {stopwatch.ElapsedMilliseconds}ms, size: {bytes.Length}b from {uri}"); +#endif + return bytes; + } + else + { + StayInTarkovHelperConstants.Logger.LogError($"Unable to send api request to server.Status code" + response.StatusCode); + return null; } } - public byte[] GetData(string url, bool hasHost = false) + public async Task GetBundleData(string url, int timeout = 60000) { - using (var dataStream = SendAndReceive(url, "GET")) - return dataStream.ToArray(); + using var cts = new CancellationTokenSource(); + return await AsyncRequestFromPath(url, cts, "GET", data: null, timeout); } - public byte[] GetBundleData(string url, int timeout = 300000) + /// + /// If a method in the Client forgets the prefix / then add it + /// + /// + private void FixUrl(ref string url) { - using (var dataStream = SendAndReceive(url, "GET", null, true, timeout)) - return dataStream.ToArray(); + // people forget the / + if (!url.StartsWith("/")) + { + url = "/" + url; + } } - public void PutJson(string url, string data, bool compress = true, int timeout = 9999, bool debug = false) + public async Task GetJsonAsync(string url, int maxRetries = 3, int delayMs = 2000) { - using (Stream stream = SendAndReceive(url, "PUT", data, compress, timeout, debug)) { } - } - - //public string GetJson(string url, bool compress = true, int timeout = 9999) - //{ - // using (MemoryStream stream = SendAndReceive(url, "GET", null, compress, timeout)) - // { - // if (stream == null) - // return ""; - // var bytes = stream.ToArray(); - // var result = Zlib.Decompress(bytes); - // bytes = null; - // return result; - // } - //} + FixUrl(ref url); - public string GetJson(string url, bool compress = true, int timeout = 9999) - { - string result = null; - int attempts = 10; - while (result == null && attempts-- > 0) + int attempt = 0; + while (attempt < maxRetries) { - using (MemoryStream stream = SendAndReceive(url, "GET", null, compress, timeout)) + using var cts = new CancellationTokenSource(); + + try { - if (stream == null) - return ""; - var bytes = stream.ToArray(); - result = Zlib.Decompress(bytes); - bytes = null; + var bytes = await AsyncRequestFromPath(url, cts, "GET"); + return Encoding.UTF8.GetString(bytes); } + catch (Exception) when (attempt < maxRetries) + { + Logger.LogWarning("[ CONFAIL ] Connection failed, retrying! (#" + attempt + ")"); + attempt++; + await Task.Delay(delayMs); + } + cts.Cancel(); } - return result; + return string.Empty; } - public string PostJson(string url, string data, bool compress = true, int timeout = 9999, bool debug = false) + public string GetJsonBLOCKING(string url) { - // people forget the / - if(!url.StartsWith("/")) - url = "/" + url; - - using (MemoryStream stream = SendAndReceive(url, "POST", data, compress, timeout, debug)) - { - return ConvertStreamToString(compress, stream); - } + return Task.Run(() => GetJsonAsync(url)).GetAwaiter().GetResult(); } - private static string ConvertStreamToString(bool compress, MemoryStream stream) - { - if (stream == null) - return ""; + private HashSet Posts = new(); - var bytes = stream.ToArray(); - string resultString; + public async Task PostJsonAsync(string url, string data, int timeout = DEFAULT_TIMEOUT_MS, int maxRetries = 3, bool debug = false) + { + FixUrl(ref url); - if (compress) - { - if (Zlib.IsCompressed(bytes)) - resultString = Zlib.Decompress(bytes); - else - resultString = Encoding.UTF8.GetString(bytes); - } - else + while (Posts.Contains(url)) { - resultString = Encoding.UTF8.GetString(bytes); + Logger.LogInfo($"{nameof(PostJsonAsync)} has held a call to {url} because its waiting for the previous call to finish!"); + await Task.Delay(timeout); } + Posts.Add(url); - return resultString; - } - - public async Task PostJsonAsync(string url, string data, bool compress = true, int timeout = DEFAULT_TIMEOUT_MS, bool debug = false, int retryAttempts = 5) - { - int attempt = 0; - Task sendReceiveTask; + int retry = 0; - while (attempt++ < retryAttempts) + do { + if (retry > 0) + Logger.LogDebug($"{nameof(PostJsonAsync)} attempt {retry} to call {url}..."); + + using var cts = new CancellationTokenSource(); try { - // people forget the / - if (!url.StartsWith("/")) - url = "/" + url; - - sendReceiveTask = SendAndReceiveAsync(url, "POST", data, compress, timeout, debug); - using (MemoryStream stream = await sendReceiveTask) + return await AsyncRequestFromPath(url, cts, "POST", data, timeout, debug) + .ContinueWith(x => { - sendReceiveTask.Dispose(); - sendReceiveTask = null; - return ConvertStreamToString(compress, stream); - } + + if (x.Status == TaskStatus.RanToCompletion) + { + if (Posts.Contains(url)) + Posts.Remove(url); + + return Encoding.UTF8.GetString(x.Result); + } + else + { + Logger.LogError($"{nameof(PostJsonAsync)}:{url}:{x.Status}"); + throw x.Exception; + } + }, cts.Token); } catch (Exception ex) { -#if DEBUG - StayInTarkovHelperConstants.Logger.LogError(new System.Diagnostics.StackTrace()); - StayInTarkovHelperConstants.Logger.LogError(ex); -#endif + // Only a Dev would care about these failures when developing Client against the Server + // Debug Log + Logger.LogDebug($"Could not perform request to {url}"); + Logger.LogDebug($"With Exception: {ex.Message}. {ex.InnerException?.Message}."); } - await Task.Delay(1000); - } - throw new Exception($"Unable to communicate with Aki Server {url} to post json data: {data}"); - } - public void PostJsonAndForgetAsync(string url, string data, bool compress = true, int timeout = DEFAULT_TIMEOUT_LONG_MS, bool debug = false) - { - Task.Run(() => PostJson(url, data, compress, timeout, debug)); - } + await Task.Delay(timeout + 1); + + if (cts != null && !cts.IsCancellationRequested) + cts.Cancel(); + + retry++; + } while (retry < maxRetries); + + if (Posts.Contains(url)) + Posts.Remove(url); + + throw new Exception($"Unable to communicate with Aki Server {url} to post json data: {data}"); + } /// - /// Retrieves data asyncronously and parses to the desired type + /// Retrieves data asyncronously and parses response JSON to the desired type /// /// Desired type to Deserialize to /// URL to call /// data to send /// - public async Task PostJsonAsync(string url, string data, int timeout = DEFAULT_TIMEOUT_MS, int retryAttempts = 5, bool debug = true) + public async Task PostJsonAsync(string url, string data, int timeout = DEFAULT_TIMEOUT_MS, int retryAttempts = 5, bool debug = false) { - int attempt = 0; - while (attempt++ < retryAttempts) - { - try - { - var json = await PostJsonAsync(url, data, compress: false, timeout: timeout, debug); - return await Task.Run(() => JsonConvert.DeserializeObject(json)); - } - catch (Exception ex) - { - StayInTarkovHelperConstants.Logger.LogError(ex); - } - } - throw new Exception($"Unable to communicate with Aki Server {url} to post json data: {data}"); + var rsp = await PostJsonAsync(url, data, timeout, retryAttempts, debug); + var obj = JsonConvert.DeserializeObject(rsp); + return obj != null ? obj : throw new Exception($"unexpected null json object after parsing response from {url}: {rsp}"); + } + + internal string PostJsonBLOCKING(string url, string data, int timeout = DEFAULT_TIMEOUT_MS) + { + return Task.Run(() => PostJsonAsync(url, data, timeout)).GetAwaiter().GetResult(); } public void Dispose() { ProfileId = null; RemoteEndPoint = null; - SITGameServerClientDataProcessing.OnLatencyUpdated -= OnLatencyUpdated; + Singleton.Instance.OnLatencyUpdated -= OnLatencyUpdated; } } } diff --git a/Source/Networking/GameClient.cs b/Source/Networking/GameClient.cs index 096ff6f5d..76fe24c01 100644 --- a/Source/Networking/GameClient.cs +++ b/Source/Networking/GameClient.cs @@ -17,29 +17,24 @@ public static class GameClient static GameClient() { Logger = BepInEx.Logging.Logger.CreateLogSource("GameClient"); + + } public static void SendData(byte[] data) { if (!Singleton.Instantiated) { - Logger.LogError($"{nameof(GameClient)}:{nameof(SendData)} {nameof(ISITGame)} has not been Instantiated"); + Logger.LogError($"{nameof(GameClient)}:{nameof(SendData)} {nameof(ISITGame)} has not been instantiated"); return; } - if (Singleton.Instance.GameClient == null) { - Logger.LogError($"{nameof(ISITGame)}:{nameof(IGameClient)} has not been Instantiated"); + Logger.LogError($"{nameof(ISITGame)}:{nameof(IGameClient)} has not been instantiated"); return; } -//#if DEBUG -// Logger.LogInfo("SendData(byte[] data)"); -// System.Diagnostics.StackTrace t = new System.Diagnostics.StackTrace(); -// Logger.LogInfo($"{t.ToString()}"); -//#endif - Singleton.Instance.GameClient.SendData(data); } diff --git a/Source/Networking/GameClientUDP.cs b/Source/Networking/GameClientUDP.cs index 9dad6e3b0..716ab2447 100644 --- a/Source/Networking/GameClientUDP.cs +++ b/Source/Networking/GameClientUDP.cs @@ -10,40 +10,22 @@ using StayInTarkov.Coop.Matchmaker; using StayInTarkov.Coop.NetworkPacket; using StayInTarkov.Coop.SITGameModes; - -//using StayInTarkov.Coop.Players; -//using StayInTarkov.Networking.Packets; using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Net; using System.Net.Sockets; -using System.Text; using System.Threading.Tasks; using UnityEngine; -using UnityEngine.Profiling; -using UnityStandardAssets.Water; -using static BackendConfigManagerConfig; -using static StayInTarkov.Networking.SITSerialization; - -/* -* This code has been written by Lacyway (https://github.com/Lacyway) for the SIT Project (https://github.com/stayintarkov/StayInTarkov.Client). -* You are free to re-use this in your own project, but out of respect please leave credit where it's due according to the MIT License. -*/ namespace StayInTarkov.Networking { public class GameClientUDP : MonoBehaviour, INetEventListener, IGameClient { - public Dictionary ServerEndPoints = new Dictionary(); public NatHelper _natHelper; private LiteNetLib.NetManager _netClient; - private NetDataWriter _dataWriter = new(); - private SITGameComponent CoopGameComponent { get; set; } - public NetPacketProcessor _packetProcessor = new(); - public int ConnectedClients = 0; + + public Dictionary ServerEndPoints { get; set; } = new(); + public NetPacketProcessor PacketProcessor { get; } = new(); public ushort Ping { get; private set; } = 0; public float DownloadSpeedKbps { get; private set; } = 0; public float UploadSpeedKbps { get; private set; } = 0; @@ -52,30 +34,11 @@ public class GameClientUDP : MonoBehaviour, INetEventListener, IGameClient void Awake() { - CoopGameComponent = CoopPatches.CoopGameComponentParent.GetComponent(); Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(GameClientUDP)); - - //PublicEndPoint = new IPEndPoint(IPAddress.Parse(StayInTarkovPlugin.SITIPAddresses.ExternalAddresses.IPAddressV4), PluginConfigSettings.Instance.CoopSettings.SITUdpPort); } public async void Start() { - _packetProcessor.RegisterNestedType(Vector3Utils.Serialize, Vector3Utils.Deserialize); - _packetProcessor.RegisterNestedType(Vector2Utils.Serialize, Vector2Utils.Deserialize); - _packetProcessor.RegisterNestedType(PhysicalUtils.Serialize, PhysicalUtils.Deserialize); - - //_packetProcessor.SubscribeNetSerializable(OnPlayerStatePacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnGameTimerPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnWeatherPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnFirearmControllerPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnHealthPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnInventoryPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnCommonPlayerPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnAllCharacterRequestPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnInformationPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnAirdropPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnAirdropLootPacketReceived); - _netClient = new LiteNetLib.NetManager(this) { UnconnectedMessagesEnabled = true, @@ -84,6 +47,7 @@ public async void Start() IPv6Enabled = false, PacketPoolSize = 999, EnableStatistics = true, + ChannelsCount = 2, }; // =============================================================== @@ -182,6 +146,13 @@ public async void Start() } else if (SITMatchmaking.IsServer) { + var serv = GetComponent(); + while (!serv._netServer.IsRunning) + { + Logger.LogDebug("Waiting for UDP server to start"); + await Task.Delay(1000); + } + // Connect locally if we're the server. var endpoint = new IPEndPoint(IPAddress.Loopback, PluginConfigSettings.Instance.CoopSettings.UdpServerLocalPort); var msg = $"Server connecting as client to {endpoint}"; @@ -191,6 +162,7 @@ public async void Start() _netClient.Connect(endpoint, "sit.core"); } } + void Update() { _netClient.PollEvents(); @@ -206,12 +178,21 @@ public void ResetStats() void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod) { - var bytes = reader.GetRemainingBytes(); - SITGameServerClientDataProcessing.ProcessPacketBytes(bytes, Encoding.UTF8.GetString(bytes)); + if (channelNumber == SITGameServerClientDataProcessing.FLATBUFFER_CHANNEL_NUM) + { + Singleton.Instance.ProcessFlatBuffer(reader.GetRemainingBytes()); + } + else + { + var bytes = reader.GetRemainingBytes(); + Singleton.Instance.ProcessPacketBytes(bytes); + } } void OnDestroy() { + Logger.LogDebug("OnDestroy()"); + if (_netClient != null) _netClient.Stop(); @@ -231,11 +212,14 @@ public void OnPeerConnected(NetPeer peer) EFT.UI.ConsoleScreen.Log("[CLIENT] We connected to " + peer.EndPoint); NotificationManagerClass.DisplayMessageNotification($"Connected to server {peer.EndPoint}.", EFT.Communications.ENotificationDurationType.Default, EFT.Communications.ENotificationIconType.Friend); + + Logger.LogDebug("[CLIENT] We connected to " + peer.EndPoint); } public void OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) { EFT.UI.ConsoleScreen.Log("[CLIENT] We received error " + socketErrorCode); + Logger.LogDebug("[CLIENT] We received error " + socketErrorCode); } public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) @@ -243,6 +227,7 @@ public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketRead if (messageType == UnconnectedMessageType.BasicMessage && _netClient.ConnectedPeersCount == 0 && reader.GetInt() == 1) { EFT.UI.ConsoleScreen.Log("[CLIENT] Received discovery response. Connecting to: " + remoteEndPoint); + Logger.LogDebug("[CLIENT] Received discovery response. Connecting to: " + remoteEndPoint); _netClient.Connect(remoteEndPoint, "sit.core"); } } @@ -260,11 +245,12 @@ public void OnConnectionRequest(LiteNetLib.ConnectionRequest request) public void OnPeerDisconnected(NetPeer peer, LiteNetLib.DisconnectInfo disconnectInfo) { EFT.UI.ConsoleScreen.Log("[CLIENT] We disconnected because " + disconnectInfo.Reason); + Logger.LogDebug("[CLIENT] We disconnected because " + disconnectInfo.Reason); } int firstPeerErrorCount = 0; - public void SendData(byte[] data) + public void SendData(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod) { if (_netClient == null) { @@ -295,30 +281,19 @@ public void SendData(byte[] data) return; } - _netClient.FirstPeer.Send(data, LiteNetLib.DeliveryMethod.ReliableOrdered); + _netClient.FirstPeer.Send(data, start, length, channelNumber, deliveryMethod); } - public void SendData(ref T packet) where T : BasePacket + public void SendData(byte[] data) { - if (_netClient == null) - { - EFT.UI.ConsoleScreen.LogError("[CLIENT] Could not communicate to the Server"); - return; - } - - if (_netClient.FirstPeer == null) - { - string clientFirstPeerIsNullMessage = "[CLIENT] Could not communicate to the Server"; - EFT.UI.ConsoleScreen.LogError(clientFirstPeerIsNullMessage); - return; - } + SendData(data, 0, data.Length, 0, LiteNetLib.DeliveryMethod.ReliableOrdered); + } + public void SendData(ref T packet) where T : BasePacket + { using NetDataWriter writer = new NetDataWriter(); - _packetProcessor.WriteNetSerializable(writer, ref packet); - if (_netClient.FirstPeer != null) - { - _netClient.FirstPeer.Send(writer, DeliveryMethod.ReliableOrdered); - } + PacketProcessor.WriteNetSerializable(writer, ref packet); + this.SendData(writer.CopyData()); } } } diff --git a/Source/Networking/GameServer.cs b/Source/Networking/GameServer.cs deleted file mode 100644 index 100515a9b..000000000 --- a/Source/Networking/GameServer.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace StayInTarkov.Networking -{ - public class GameServer - { - } -} diff --git a/Source/Networking/GameServerUDP.cs b/Source/Networking/GameServerUDP.cs index 624d55b91..03fe9daa1 100644 --- a/Source/Networking/GameServerUDP.cs +++ b/Source/Networking/GameServerUDP.cs @@ -27,26 +27,13 @@ namespace StayInTarkov.Networking public class GameServerUDP : MonoBehaviour, INetEventListener, INetLogger { public NatHelper _natHelper; - private LiteNetLib.NetManager _netServer; - public NetPacketProcessor _packetProcessor = new(); - private NetDataWriter _dataWriter = new(); - public CoopPlayer MyPlayer => Singleton.Instance.MainPlayer as CoopPlayer; - public ConcurrentDictionary Players => CoopGameComponent.Players; - public List PlayersMissing = []; - private SITGameComponent CoopGameComponent { get; set; } - public LiteNetLib.NetManager NetServer - { - get - { - return _netServer; - } - } - + public LiteNetLib.NetManager _netServer; + public NetPacketProcessor _packetProcessor { get; } = new(); + private NetDataWriter _dataWriter { get; } = new(); private ManualLogSource Logger { get; set; } void Awake() { - CoopGameComponent = CoopPatches.CoopGameComponentParent.GetComponent(); Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(GameServerUDP)); } @@ -54,21 +41,6 @@ public async void Start() { NetDebug.Logger = this; - _packetProcessor.RegisterNestedType(Vector3Utils.Serialize, Vector3Utils.Deserialize); - _packetProcessor.RegisterNestedType(Vector2Utils.Serialize, Vector2Utils.Deserialize); - _packetProcessor.RegisterNestedType(PhysicalUtils.Serialize, PhysicalUtils.Deserialize); - - //_packetProcessor.SubscribeNetSerializable(OnPlayerStatePacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnGameTimerPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnWeatherPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnWeaponPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnHealthPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnInventoryPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnCommonPlayerPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnAllCharacterRequestPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnInformationPacketReceived); - //_packetProcessor.SubscribeNetSerializable(OnPlayerProceedPacket); - _netServer = new LiteNetLib.NetManager(this) { BroadcastReceiveEnabled = true, @@ -76,7 +48,8 @@ public async void Start() AutoRecycle = true, IPv6Enabled = false, EnableStatistics = true, - NatPunchEnabled = false + NatPunchEnabled = false, + ChannelsCount = 2 }; // =============================================================== @@ -142,201 +115,13 @@ public async void Start() EFT.UI.ConsoleScreen.Log(msg); } - //private void OnPlayerProceedPacket(PlayerProceedPacket packet, NetPeer peer) - //{ - // Logger.LogInfo("[Server] OnPlayerProceedPacket"); - // Logger.LogInfo(packet.ToJson()); - //} - void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod) { //Logger.LogInfo("[Server] OnNetworkReceive"); var bytes = reader.GetRemainingBytes(); - _netServer.SendToAll(bytes, deliveryMethod); + _netServer.SendToAll(bytes, channelNumber, deliveryMethod); } - //private void OnInformationPacketReceived(InformationPacket packet, NetPeer peer) - //{ - // InformationPacket respondPackage = new(false) - // { - // NumberOfPlayers = _netServer.ConnectedPeersCount - // }; - - // _dataWriter.Reset(); - // SendDataToPeer(peer, _dataWriter, ref respondPackage, DeliveryMethod.ReliableUnordered); - //} - - //private void OnAllCharacterRequestPacketReceived(AllCharacterRequestPacket packet, NetPeer peer) - //{ - // // This method needs to be refined. For some reason the ping-pong has to be run twice for it to work on the host? - // if (packet.IsRequest) - // { - // foreach (var player in CoopGameComponent.Players.Values) - // { - // if (player.ProfileId == packet.ProfileId) - // continue; - - // if (packet.Characters.Contains(player.ProfileId)) - // continue; - - // AllCharacterRequestPacket requestPacket = new(player.ProfileId) - // { - // IsRequest = false, - // PlayerInfo = new() - // { - // Profile = player.Profile - // }, - // IsAlive = player.ActiveHealthController.IsAlive, - // Position = player.Transform.position - // }; - // _dataWriter.Reset(); - // SendDataToPeer(peer, _dataWriter, ref requestPacket, DeliveryMethod.ReliableUnordered); - // } - // } - // if (!Players.ContainsKey(packet.ProfileId) && !PlayersMissing.Contains(packet.ProfileId)) - // { - // PlayersMissing.Add(packet.ProfileId); - // EFT.UI.ConsoleScreen.Log($"Requesting missing player from server."); - // AllCharacterRequestPacket requestPacket = new(MyPlayer.ProfileId); - // _dataWriter.Reset(); - // SendDataToPeer(peer, _dataWriter, ref requestPacket, DeliveryMethod.ReliableUnordered); - // } - // if (!packet.IsRequest && PlayersMissing.Contains(packet.ProfileId)) - // { - // EFT.UI.ConsoleScreen.Log($"Received CharacterRequest from client: ProfileID: {packet.PlayerInfo.Profile.ProfileId}, Nickname: {packet.PlayerInfo.Profile.Nickname}"); - // if (packet.ProfileId != MyPlayer.ProfileId) - // { - // if (!CoopGameComponent.PlayersToSpawn.ContainsKey(packet.PlayerInfo.Profile.ProfileId)) - // CoopGameComponent.PlayersToSpawn.TryAdd(packet.PlayerInfo.Profile.ProfileId, ESpawnState.None); - // if (!CoopGameComponent.PlayersToSpawnProfiles.ContainsKey(packet.PlayerInfo.Profile.ProfileId)) - // CoopGameComponent.PlayersToSpawnProfiles.Add(packet.PlayerInfo.Profile.ProfileId, packet.PlayerInfo.Profile); - - // //CoopGameComponent.QueueProfile(packet.PlayerInfo.Profile, new Vector3(packet.Position.x, packet.Position.y + 0.5f, packet.Position.y), packet.IsAlive); - // PlayersMissing.Remove(packet.ProfileId); - // } - // } - //} - //private void OnCommonPlayerPacketReceived(CommonPlayerPacket packet, NetPeer peer) - //{ - // if (!Players.ContainsKey(packet.ProfileId)) - // return; - - // _dataWriter.Reset(); - // SendDataToAll(_dataWriter, ref packet, DeliveryMethod.ReliableOrdered, peer); - - // var playerToApply = Players[packet.ProfileId] as CoopPlayerClient; - // if (playerToApply != default && playerToApply != null) - // { - // //playerToApply.CommonPlayerPackets.Enqueue(packet); - // } - //} - //private void OnInventoryPacketReceived(ItemPlayerPacket packet, NetPeer peer) - //{ - // if (!Players.ContainsKey(packet.ProfileId)) - // return; - - // _dataWriter.Reset(); - // SendDataToAll(_dataWriter, ref packet, DeliveryMethod.ReliableOrdered, peer); - - // var playerToApply = Players[packet.ProfileId] as CoopPlayerClient; - // if (playerToApply != default && playerToApply != null) - // { - // //playerToApply.InventoryPackets.Enqueue(packet); - // } - //} - //private void OnHealthPacketReceived(HealthPacket packet, NetPeer peer) - //{ - // if (!Players.ContainsKey(packet.ProfileId)) - // return; - - // _dataWriter.Reset(); - // SendDataToAll(_dataWriter, ref packet, DeliveryMethod.ReliableOrdered, peer); - - // var playerToApply = Players[packet.ProfileId] as CoopPlayerClient; - // if (playerToApply != default && playerToApply != null) - // { - // //playerToApply.HealthPackets.Enqueue(packet); - // } - //} - //private void OnWeaponPacketReceived(WeaponPacket packet, NetPeer peer) - //{ - // if (!Players.ContainsKey(packet.ProfileId)) - // return; - - // _dataWriter.Reset(); - // SendDataToAll(_dataWriter, ref packet, DeliveryMethod.ReliableOrdered, peer); - - // var playerToApply = Players[packet.ProfileId] as CoopPlayerClient; - // if (playerToApply != default && playerToApply != null && !playerToApply.IsYourPlayer) - // { - // //playerToApply.FirearmPackets.Enqueue(packet); - // } - //} - //private void OnWeatherPacketReceived(WeatherPacket packet, NetPeer peer) - //{ - // if (!packet.IsRequest) - // return; - - // var weatherController = WeatherController.Instance; - // if (weatherController != null) - // { - // WeatherPacket weatherPacket = new(); - // if (weatherController.CloudsController != null) - // weatherPacket.CloudDensity = weatherController.CloudsController.Density; - - // var weatherCurve = weatherController.WeatherCurve; - // if (weatherCurve != null) - // { - // weatherPacket.Fog = weatherCurve.Fog; - // weatherPacket.LightningThunderProbability = weatherCurve.LightningThunderProbability; - // weatherPacket.Rain = weatherCurve.Rain; - // weatherPacket.Temperature = weatherCurve.Temperature; - // weatherPacket.WindX = weatherCurve.Wind.x; - // weatherPacket.WindY = weatherCurve.Wind.y; - // weatherPacket.TopWindX = weatherCurve.TopWind.x; - // weatherPacket.TopWindY = weatherCurve.TopWind.y; - // } - - // _dataWriter.Reset(); - // SendDataToPeer(peer, _dataWriter, ref weatherPacket, DeliveryMethod.ReliableOrdered); - // } - //} - //private void OnGameTimerPacketReceived(GameTimerPacket packet, NetPeer peer) - //{ - // if (!packet.IsRequest) - // return; - - // var game = (CoopGame)Singleton.Instance; - // if (game != null) - // { - // GameTimerPacket gameTimerPacket = new(false, (game.GameTimer.SessionTime - game.GameTimer.PastTime).Value.Ticks); - // _dataWriter.Reset(); - // SendDataToPeer(peer, _dataWriter, ref gameTimerPacket, DeliveryMethod.ReliableOrdered); - // } - // else - // { - // EFT.UI.ConsoleScreen.Log("OnGameTimerPacketReceived: Game was null!"); - // } - //} - - //private void OnPlayerStatePacketReceived(PlayerStatePacket packet, NetPeer peer) - //{ - - // Logger.LogInfo($"{nameof(OnPlayerStatePacketReceived)}"); - - // if (!Players.ContainsKey(packet.ProfileId)) - // return; - - // _dataWriter.Reset(); - // SendDataToAll(_dataWriter, ref packet, DeliveryMethod.ReliableOrdered, peer); - - // var playerToApply = Players[packet.ProfileId] as CoopPlayerClient; - // if (playerToApply != default && playerToApply != null && !playerToApply.IsYourPlayer) - // { - // playerToApply.NewState = packet; - // } - //} - void Update() { _netServer.PollEvents(); @@ -344,6 +129,7 @@ void Update() void OnDestroy() { + Logger.LogDebug("OnDestroy()"); NetDebug.Logger = null; if (_netServer != null) diff --git a/Source/Networking/SITGameServerClientDataProcessing.cs b/Source/Networking/SITGameServerClientDataProcessing.cs index 2f2748f3c..671c3c466 100644 --- a/Source/Networking/SITGameServerClientDataProcessing.cs +++ b/Source/Networking/SITGameServerClientDataProcessing.cs @@ -14,134 +14,105 @@ using Mono.Cecil; using StayInTarkov.Coop.SITGameModes; using StayInTarkov.Coop.NetworkPacket.Player; +using UnityEngine.Networking; +using System.Threading; +using UnityEngine; +using Google.FlatBuffers; +using StayInTarkov.FlatBuffers; +using StayInTarkov.Coop.Players; namespace StayInTarkov.Networking { - public static class SITGameServerClientDataProcessing + public class SITGameServerClientDataProcessing : NetworkBehaviour { - public const string PACKET_TAG_METHOD = "m"; - public const string PACKET_TAG_SERVERID = "serverId"; - public const string PACKET_TAG_DATA = "data"; + public const byte FLATBUFFER_CHANNEL_NUM = 1; + public event Action OnLatencyUpdated; + private SITGameComponent SITGameComponent { get; set; } - public static event Action OnLatencyUpdated; + public ManualLogSource Logger { get; set; } - public static ManualLogSource Logger { get; } - - static SITGameServerClientDataProcessing() + void Awake() { Logger = BepInEx.Logging.Logger.CreateLogSource($"{nameof(SITGameServerClientDataProcessing)}"); } - public static void ProcessPacketBytes(byte[] data, string sData) + void Update() { - try + if (Singleton.Instantiated) { - if (data == null) + if ((Singleton.Instance as MonoBehaviour).TryGetComponent(out var comp)) { - Logger.LogError($"{nameof(ProcessPacketBytes)}. Data is Null"); - return; + SITGameComponent = comp; } - - if (data.Length == 0) + else { - Logger.LogError($"{nameof(ProcessPacketBytes)}. Data is Empty"); - return; + SITGameComponent = null; } + } + } - Dictionary packet = null; - ISITPacket sitPacket = null; + public void ProcessFlatBuffer(byte[] data) + { + var buf = new ByteBuffer(data); + var packet = StayInTarkov.FlatBuffers.Packet.GetRootAsPacket(buf); - // Is a dictionary from Spt-Aki - if (!string.IsNullOrEmpty(sData) && sData.StartsWith("{")) - { - // Use StreamReader & JsonTextReader to improve memory / cpu usage - using (var streamReader = new StreamReader(new MemoryStream(data))) + if (SITGameComponent == null) + { + Logger.LogError($"{nameof(ProcessFlatBuffer)}. game component is null"); + return; + } + + switch (packet.PacketType) + { + case AnyPacket.player_state: { - using (var reader = new JsonTextReader(streamReader)) + var key = packet.Packet_Asplayer_state().ProfileId; + if (SITGameComponent.Players.ContainsKey(key) && SITGameComponent.Players[key] is CoopPlayerClient client) { - var serializer = new JsonSerializer(); - packet = serializer.Deserialize>(reader); + client.ReceivePlayerStatePacket(packet.Packet_Asplayer_state()); } } - } - // Is a RAW SIT Serialized packet - else - { - ProcessSITPacket(data, ref packet, out sitPacket); - } - - var coopGameComponent = SITGameComponent.GetCoopGameComponent(); - - if (coopGameComponent == null) - { - Logger.LogError($"{nameof(ProcessPacketBytes)}. coopGameComponent is Null"); - return; - } - - if (packet == null) - { - //Logger.LogError($"{nameof(ProcessPacketBytes)}. Packet is Null"); - return; - } + break; + default: + { + Logger.LogWarning("unknown FlatBuffer type received"); + } + break; + } + } - // If this is a pong packet, resolve and create a smooth ping - if (packet.ContainsKey("pong")) + public void ProcessPacketBytes(byte[] data) + { + try + { + if (data == null) { - var pongRaw = long.Parse(packet["pong"].ToString()); - var dtPong = new DateTime(pongRaw); - var latencyMs = (DateTime.UtcNow - dtPong).TotalMilliseconds / 2; - OnLatencyUpdated((ushort)latencyMs); + Logger.LogError($"{nameof(ProcessPacketBytes)}. Data is Null"); return; } - // Receiving a Player Extracted packet. Process into ExtractedPlayers List - if (packet.ContainsKey("Extracted")) + if (data.Length == 0) { - if (Singleton.Instantiated && !Singleton.Instance.ExtractedPlayers.Contains(packet["profileId"].ToString())) - { - Singleton.Instance.ExtractedPlayers.Add(packet["profileId"].ToString()); - } + Logger.LogError($"{nameof(ProcessPacketBytes)}. Data is Empty"); return; } - // If this is an endSession packet, end the session for the clients - if (packet.ContainsKey("endSession") && SITMatchmaking.IsClient) - { - Logger.LogDebug("Received EndSession from Server. Ending Game."); - if (coopGameComponent.LocalGameInstance == null) - return; - - coopGameComponent.ServerHasStopped = true; + if (SITGameComponent == null) return; - } - // ------------------------------------------------------- - // Add to the Coop Game Component Action Packets - if (coopGameComponent == null || coopGameComponent.ActionPackets == null || coopGameComponent.ActionPacketHandler == null) - return; + ISITPacket sitPacket = null; + ProcessSITPacket(data, out sitPacket); - //if (packet.ContainsKey(PACKET_TAG_METHOD) - // && packet[PACKET_TAG_METHOD].ToString() == "Move") - // coopGameComponent.ActionPacketHandler.ActionPacketsMovement.TryAdd(packet); - //else if (packet.ContainsKey(PACKET_TAG_METHOD) - // && packet[PACKET_TAG_METHOD].ToString() == "ApplyDamageInfo") - //{ - // coopGameComponent.ActionPacketHandler.ActionPacketsDamage.TryAdd(packet); - //} - //else - //{ if (sitPacket != null) - coopGameComponent.ActionPacketHandler.ActionSITPackets.Add(sitPacket); + SITGameComponent.ActionPacketHandler.ActionSITPackets.Add(sitPacket); else { #if DEBUG Logger.LogDebug($">> DEV TODO <<"); Logger.LogDebug($">> Convert the following packet to binary <<"); - Logger.LogDebug($"{packet.ToJson()}"); + Logger.LogDebug($"{Encoding.UTF8.GetString(data)}"); #endif - coopGameComponent.ActionPacketHandler.ActionPackets.TryAdd(packet); } - //} } catch (Exception ex) @@ -150,17 +121,10 @@ public static void ProcessPacketBytes(byte[] data, string sData) } } - public static void ProcessSITPacket(byte[] data, ref Dictionary dictObject, out ISITPacket packet) + public void ProcessSITPacket(byte[] data, out ISITPacket packet) { packet = null; - var coopGameComponent = SITGameComponent.GetCoopGameComponent(); - if (coopGameComponent == null) - { - Logger.LogError($"{nameof(ProcessSITPacket)}. coopGameComponent is Null"); - return; - } - // If the data is empty. Return; if (data == null || data.Length == 0) { @@ -177,9 +141,9 @@ public static void ProcessSITPacket(byte[] data, ref Dictionary var serverId = stringData.Substring(3, 24); // If the serverId is not the same as the one we are connected to. Return; - if (serverId != coopGameComponent.ServerId) + if (serverId != SITGameComponent.ServerId) { - Logger.LogError($"{nameof(ProcessSITPacket)}. {serverId} does not equal {coopGameComponent.ServerId}"); + Logger.LogError($"{nameof(ProcessSITPacket)}. {serverId} does not equal {SITGameComponent.ServerId}"); return; } @@ -187,27 +151,10 @@ public static void ProcessSITPacket(byte[] data, ref Dictionary using (var br = new BinaryReader(new MemoryStream(data))) bp.ReadHeader(br); - dictObject = new Dictionary(); - dictObject[PACKET_TAG_DATA] = data; - dictObject[PACKET_TAG_METHOD] = bp.Method; - - if (!dictObject.ContainsKey("profileId")) - { - try - { - var bpp = new BasePlayerPacket("", dictObject[PACKET_TAG_METHOD].ToString()); - bpp.Deserialize(data); - dictObject.Add("profileId", new string(bpp.ProfileId.ToCharArray())); - bpp.Dispose(); - bpp = null; - } - catch { } - } - packet = DeserializeIntoPacket(data, packet, bp); } - private static ISITPacket DeserializeIntoPacket(byte[] data, ISITPacket packet, BasePacket bp) + private ISITPacket DeserializeIntoPacket(byte[] data, ISITPacket packet, BasePacket bp) { var sitPacketType = StayInTarkovHelperConstants @@ -229,43 +176,5 @@ private static ISITPacket DeserializeIntoPacket(byte[] data, ISITPacket packet, return packet; } - - public static bool ProcessDataListPacket(ref Dictionary packet) - { - var coopGC = SITGameComponent.GetCoopGameComponent(); - if (coopGC == null) - return false; - - if (!packet.ContainsKey("dataList")) - return false; - - JArray dataList = JArray.FromObject(packet["dataList"]); - - //Logger.LogDebug(packet.ToJson()); - - foreach (var d in dataList) - { - // TODO: This needs to be a little more dynamic but for now. This switch will do. - // Depending on the method defined, deserialize packet to defined type - switch (packet[PACKET_TAG_METHOD].ToString()) - { - case "PlayerStates": - PlayerStatePacket playerStatePacket = new PlayerStatePacket(); - playerStatePacket = (PlayerStatePacket)playerStatePacket.Deserialize((byte[])d); - if (playerStatePacket == null || string.IsNullOrEmpty(playerStatePacket.ProfileId)) - continue; - - if (coopGC.Players.ContainsKey(playerStatePacket.ProfileId)) - coopGC.Players[playerStatePacket.ProfileId].ReceivePlayerStatePacket(playerStatePacket); - - break; - case "Multiple": - break; - } - - } - - return true; - } } } diff --git a/Source/Networking/SITSerialization.cs b/Source/Networking/SITSerialization.cs index 8e0279acc..f55af6e15 100644 --- a/Source/Networking/SITSerialization.cs +++ b/Source/Networking/SITSerialization.cs @@ -1,21 +1,9 @@ -using Comfort.Common; -using ComponentAce.Compression.Libs.zlib; +using ComponentAce.Compression.Libs.zlib; using EFT; -using EFT.InventoryLogic; using LiteNetLib.Utils; -using Newtonsoft.Json; using StayInTarkov.Coop.NetworkPacket; -using System; -using System.Collections.Generic; using System.IO; -using System.Net.Sockets; -using System.Runtime.CompilerServices; -using System.Security.Policy; -using System.Xml; using UnityEngine; -using UnityEngine.Profiling; -using static Physical; -using static StayInTarkov.Networking.SITSerialization; // GClass2800 @@ -77,28 +65,28 @@ public static Vector2 Deserialize(BinaryReader reader) public class PhysicalUtils { - public static void Serialize(NetDataWriter writer, Physical.PhysicalStamina physicalStamina) + public static void Serialize(NetDataWriter writer, PhysicalStamina physicalStamina) { writer.Put(physicalStamina.StaminaExhausted); writer.Put(physicalStamina.OxygenExhausted); writer.Put(physicalStamina.HandsExhausted); } - public static void Serialize(BinaryWriter writer, Physical.PhysicalStamina physicalStamina) + public static void Serialize(BinaryWriter writer, PhysicalStamina physicalStamina) { writer.Write(physicalStamina.StaminaExhausted); writer.Write(physicalStamina.OxygenExhausted); writer.Write(physicalStamina.HandsExhausted); } - public static Physical.PhysicalStamina Deserialize(NetDataReader reader) + public static PhysicalStamina Deserialize(NetDataReader reader) { - return new Physical.PhysicalStamina() { StaminaExhausted = reader.GetBool(), OxygenExhausted = reader.GetBool(), HandsExhausted = reader.GetBool() }; + return new PhysicalStamina() { StaminaExhausted = reader.GetBool(), OxygenExhausted = reader.GetBool(), HandsExhausted = reader.GetBool() }; } - public static Physical.PhysicalStamina Deserialize(BinaryReader reader) + public static PhysicalStamina Deserialize(BinaryReader reader) { - return new Physical.PhysicalStamina() { StaminaExhausted = reader.ReadBoolean(), OxygenExhausted = reader.ReadBoolean(), HandsExhausted = reader.ReadBoolean() }; + return new PhysicalStamina() { StaminaExhausted = reader.ReadBoolean(), OxygenExhausted = reader.ReadBoolean(), HandsExhausted = reader.ReadBoolean() }; } } @@ -1003,20 +991,20 @@ public static void Serialize(NetDataWriter writer, RemoveEffectPacket packet) } } - //public struct ItemMovementHandlerMovePacket + //public struct InteractionsHandlerClassMovePacket //{ // public string ItemId { get; set; } // public AbstractDescriptor Descriptor { get; set; } - // public static ItemMovementHandlerMovePacket Deserialize(NetDataReader reader) + // public static InteractionsHandlerClassMovePacket Deserialize(NetDataReader reader) // { // GClass1035 polyReader = new(reader.RawData); - // return new ItemMovementHandlerMovePacket() + // return new InteractionsHandlerClassMovePacket() // { // ItemId = polyReader.ReadString(), // Descriptor = polyReader.ReadPolymorph() // }; // } - // public static void Serialize(NetDataWriter writer, ItemMovementHandlerMovePacket packet) + // public static void Serialize(NetDataWriter writer, InteractionsHandlerClassMovePacket packet) // { // GClass1040 polyWriter = new(); // polyWriter.WriteString(packet.ItemId); diff --git a/Source/StayInTarkov.csproj b/Source/StayInTarkov.csproj index ea69d4f58..ee803ebe2 100644 --- a/Source/StayInTarkov.csproj +++ b/Source/StayInTarkov.csproj @@ -6,9 +6,10 @@ Stay in Tarkov plugin for Escape From Tarkov 1.0.0 true - latest - false - false + preview + false + false + Copyright @ Stay In Tarkov 2024 @@ -30,8 +31,8 @@ - - + + @@ -56,7 +57,7 @@ ..\References\Assembly-CSharp-firstpass.dll - False + False ..\References\bsg.componentace.compression.libs.zlib.dll @@ -82,10 +83,10 @@ ..\References\DissonanceVoip.dll False - - ..\References\FilesChecker.dll - False - + + ..\References\FilesChecker.dll + False + ..\References\ItemComponent.Types.dll false @@ -138,22 +139,25 @@ ..\References\UnityEngine.UIElementsModule.dll - ..\References\UnityEngine.UIModule.dll - - - ..\References\websocket-sharp.dll - False - - - ..\References\Newtonsoft.Json.dll - False - - + ..\References\UnityEngine.UIModule.dll + + + ..\References\websocket-sharp.dll + False + + + ..\References\Newtonsoft.Json.dll + False + + + - + + + diff --git a/Source/StayInTarkovPlugin.cs b/Source/StayInTarkovPlugin.cs index b4fd8b5ec..bf600e5ae 100644 --- a/Source/StayInTarkovPlugin.cs +++ b/Source/StayInTarkovPlugin.cs @@ -17,8 +17,6 @@ using StayInTarkov.EssentialPatches.Web; using StayInTarkov.FileChecker; using StayInTarkov.Health; -using StayInTarkov.Networking; -using StayInTarkov.ThirdParty; using StayInTarkov.UI; using System; using System.Collections; @@ -29,7 +27,12 @@ using System.Threading; using BepInEx.Configuration; using UnityEngine; +using StayInTarkov.Tools; +using System.Threading.Tasks; using StayInTarkov.AkiSupport.Singleplayer.Patches.RaidFix; +using System.Reflection; +using HarmonyLib; +using TMPro; namespace StayInTarkov { @@ -38,8 +41,13 @@ namespace StayInTarkov /// Written by: Paulov /// Used template by BepInEx /// - [BepInPlugin("com.stayintarkov", "StayInTarkov", "1.10.0")] + [BepInPlugin("com.stayintarkov", "StayInTarkov", "1.11")] [BepInProcess("EscapeFromTarkov.exe")] + // Ensure nobody tries to load this module with Fika. They wont be compatible :) + [BepInIncompatibility("com.fika.core")] + //[BepInDependency("com.spt-aki.core", BepInDependency.DependencyFlags.SoftDependency)] + [BepInDependency("com.spt-aki.singleplayer", BepInDependency.DependencyFlags.SoftDependency)] + [BepInDependency("com.spt-aki.custom", BepInDependency.DependencyFlags.SoftDependency)] public class StayInTarkovPlugin : BaseUnityPlugin { /// @@ -76,28 +84,64 @@ public class StayInTarkovPlugin : BaseUnityPlugin public static bool LanguageDictionaryLoaded { get; private set; } - internal static string IllegalMessage { get; } = LanguageDictionaryLoaded && LanguageDictionary.ContainsKey("ILLEGAL_MESSAGE") ? LanguageDictionary["ILLEGAL_MESSAGE"].ToString() : "Illegal game found. Please buy, install and launch the game once."; - void Awake() + public delegate void OnGameLoadedHandler(object sender, EventArgs e); + public event OnGameLoadedHandler OnGameLoaded; + + private string[] SPTPatchesToRemove => [ + "AddEnemyToAllGroupsInBotZonePatch", + "AddSptBotSettingsPatch", // Requires Aki.PrePatch + "AirdropPatch", + "AirdropFlarePatch", + "AmmoUsedCounterPatch", + "ArmorDamageCounterPatch", + "BotDifficultyPatch", + "BotTemplateLimitPatch", + "BTRInteractionPatch", + "BTRExtractPassengersPatch", + "BTRPatch", + "CustomAiPatch", // Requires Aki.PrePatch + "DogtagPatch", + "EmptyInfilFixPatch", + "LabsKeycardRemovalPatch", + "LoadOfflineRaidScreenPatch", + "MaxBotPatch", + "OfflineSpawnPointPatch", + "OfflineRaidSettingsMenuPatch", + "PmcFirstAidPatch", // Requires Aki.PrePatch + "ScavExfilPatch", + "ScavLateStartPatch", + "ScavLateStartPatch", + "ScavProfileLoadPatch", + "ScavRepAdjustmentPatch", + "ScavSellAllPriceStorePatch", + "ScavSellAllRequestPatch", + "VersionLabelPatch" + ]; + + async Task Awake() { Instance = this; Settings = new PluginConfigSettings(Logger, Config); LogDependancyErrors(); + DisableSPT(); + // Gather the Major/Minor numbers of EFT ASAP new VersionLabelPatch(Config).Enable(); + OnGameLoaded += StayInTarkovPlugin_OnGameLoaded; StartCoroutine(VersionChecks()); ReadInLanguageDictionary(); EnableCorePatches(); - EnableBundlePatches(); + await EnableBundlePatches(); EnableSPPatches(); @@ -105,17 +149,138 @@ void Awake() EnableAirdropPatches(); - ThirdPartyModPatches.Run(Config, this); + EnableAiPatches(); + + if (Autoraid.Requested()) + { + Logger.LogInfo($"Running autoraid"); + gameObject.GetOrAddComponent(); + } Logger.LogInfo($"Stay in Tarkov is loaded!"); } + void DisableSPT() + { + DisableAkiSingleplayer(); + DisableAkiCustom(); + } + + private void StayInTarkovPlugin_OnGameLoaded(object sender, EventArgs e) + { + // Log the list + LogLoadedPlugins(); + } + + + + private void DisableAkiSingleplayer() + { + if (Chainloader.PluginInfos.Any(x => x.Key == "com.spt-aki.singleplayer")) + { + Logger.LogInfo($"AkiSingleplayerPlugin detected. Removing."); + + var akiPlugin = Chainloader.ManagerObject.GetComponent("AkiSingleplayerPlugin"); + if (akiPlugin == null) + { + Logger.LogError($"Unable to find Singleplayer"); + return; + } + + var akiPluginType = akiPlugin.GetType(); + if (akiPluginType == null) + return; + + var akiPluginModulePatchTypes = akiPluginType.Assembly.GetTypes() + .Where(x => x.BaseType != null && x.BaseType.Name == "ModulePatch").ToArray(); + + if (!akiPluginModulePatchTypes.Any()) + return; + + foreach (var removeType in SPTPatchesToRemove) + { + RemovePatch(akiPluginModulePatchTypes, removeType); + } + } + } + + /// + /// Detect and Disable Aki Custom Plugin + /// + private void DisableAkiCustom() + { + if(Chainloader.PluginInfos.Any(x=>x.Key == "com.spt-aki.custom")) + { + Logger.LogInfo($"Aki Custom detected. Removing."); + + var akiPlugin = Chainloader.ManagerObject.GetComponent("AkiCustomPlugin"); + if (akiPlugin == null) + { + Logger.LogError($"Unable to find AkiCustomPlugin"); + } + + var akiPluginType = akiPlugin.GetType(); + + var akiPluginModulePatchTypes = akiPluginType.Assembly.GetTypes() + .Where(x => x.BaseType != null && x.BaseType.Name == "ModulePatch"); + + if (!akiPluginModulePatchTypes.Any()) + return; + + foreach (var removeType in SPTPatchesToRemove) + { + RemovePatch(akiPluginModulePatchTypes, removeType); + } + + } + } + + private void RemovePatch(IEnumerable types, string typeToRemove) + { + var p = types.FirstOrDefault(x => x.Name == typeToRemove); + if (p != null) + RemovePatch(p); + } - private void EnableBundlePatches() + private void RemovePatch(Type typeToRemove) + { + ReflectionHelpers.GetMethodForType(typeToRemove, "Disable").Invoke(Activator.CreateInstance(typeToRemove), []); + Logger.LogInfo($"-> Removed {typeToRemove.FullName}"); + } + + public void LogLoadedPlugins() + { +#if DEBUG + Logger.LogDebug(nameof(LogLoadedPlugins)); +#endif + + Logger.LogDebug($"Plugin's loaded:"); + + foreach (var plugin in Chainloader.PluginInfos) + { + Logger.LogDebug($"- {plugin.Key} {plugin.Value.Metadata.Name}"); + } + } + public bool IsAkiCoreLoaded() + { + return Chainloader.PluginInfos.ContainsKey("com.spt-aki.core"); + } + + public bool IsAkiSinglePlayerLoaded() + { + return Chainloader.PluginInfos.ContainsKey("com.spt-aki.singleplayer"); + } + + public bool IsAkiCustomLoaded() + { + return Chainloader.PluginInfos.ContainsKey("com.spt-aki.custom"); + } + + private async Task EnableBundlePatches() { try { - BundleManager.GetBundles(); + await BundleManager.GetBundles(); new EasyAssetsPatch().Enable(); new EasyBundlePatch().Enable(); } @@ -134,6 +299,11 @@ private void EnableAirdropPatches() new AirdropFlarePatch().Enable(); } + private void EnableAiPatches() + { + new CustomAiPatch().Enable(); + } + private bool shownCheckError = false; private bool bsgThanksShown = false; @@ -249,10 +419,10 @@ private IEnumerator VersionChecks() var majorN1 = EFTVersionMajor.Split('.')[0]; // 0 var majorN2 = EFTVersionMajor.Split('.')[1]; // 14 var majorN3 = EFTVersionMajor.Split('.')[2]; // 1 - var majorN4 = EFTVersionMajor.Split('.')[3]; // 3 + var majorN4 = EFTVersionMajor.Split('.')[3]; // 2 var majorN5 = EFTVersionMajor.Split('.')[4]; // build number - if (majorN1 != "0" || majorN2 != "14" || majorN3 != "1" || majorN4 != "3") + if (majorN1 != "0" || majorN2 != "14" || majorN3 != "1" || majorN4 != "2") { Logger.LogError( "Version Check: This version of SIT is not designed to work with this version of EFT."); @@ -263,6 +433,8 @@ private IEnumerator VersionChecks() } } + this.OnGameLoaded?.Invoke(this, null); + break; } } @@ -292,9 +464,10 @@ private void EnableCorePatches() var url = DetectBackendUrlAndToken.GetBackendConnection().BackendUrl; if (!url.Contains("https")) { + WebSocketPatch.IsHttps = false; new TransportPrefixPatch().Enable(); - new WebSocketPatch().Enable(); } + new WebSocketPatch().Enable(); } catch (Exception e) { @@ -387,6 +560,9 @@ private static void EnableSPPatches_Bots(BepInEx.Configuration.ConfigFile config new LocationLootCacheBustingPatch().Enable(); new FixBrokenSpawnOnSandboxPatch().Enable(); new IsEnemyPatch().Enable(); + + + //new BlockerErrorFixPatch().Enable(); } private void EnableCoopPatches() diff --git a/Source/ThirdParty/FlatBuffers/ByteBuffer.cs b/Source/ThirdParty/FlatBuffers/ByteBuffer.cs new file mode 100644 index 000000000..4c0d000a0 --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/ByteBuffer.cs @@ -0,0 +1,1041 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// There are three conditional compilation symbols that have an impact on performance/features of this ByteBuffer implementation. +// +// UNSAFE_BYTEBUFFER +// This will use unsafe code to manipulate the underlying byte array. This +// can yield a reasonable performance increase. +// +// BYTEBUFFER_NO_BOUNDS_CHECK +// This will disable the bounds check asserts to the byte array. This can +// yield a small performance gain in normal code. +// +// ENABLE_SPAN_T +// This will enable reading and writing blocks of memory with a Span instead of just +// T[]. You can also enable writing directly to shared memory or other types of memory +// by providing a custom implementation of ByteBufferAllocator. +// ENABLE_SPAN_T also requires UNSAFE_BYTEBUFFER to be defined, or .NET +// Standard 2.1. +// +// Using UNSAFE_BYTEBUFFER and BYTEBUFFER_NO_BOUNDS_CHECK together can yield a +// performance gain of ~15% for some operations, however doing so is potentially +// dangerous. Do so at your own risk! +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) +using System.Buffers.Binary; +#endif + +#if ENABLE_SPAN_T && !UNSAFE_BYTEBUFFER && !NETSTANDARD2_1 +#warning ENABLE_SPAN_T requires UNSAFE_BYTEBUFFER to also be defined +#endif + +namespace Google.FlatBuffers +{ + public abstract class ByteBufferAllocator + { +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + public abstract Span Span { get; } + public abstract ReadOnlySpan ReadOnlySpan { get; } + public abstract Memory Memory { get; } + public abstract ReadOnlyMemory ReadOnlyMemory { get; } + +#else + public byte[] Buffer + { + get; + protected set; + } +#endif + + public int Length + { + get; + protected set; + } + + public abstract void GrowFront(int newSize); + } + + public sealed class ByteArrayAllocator : ByteBufferAllocator + { + private byte[] _buffer; + + public ByteArrayAllocator(byte[] buffer) + { + _buffer = buffer; + InitBuffer(); + } + + public override void GrowFront(int newSize) + { + if ((Length & 0xC0000000) != 0) + throw new Exception( + "ByteBuffer: cannot grow buffer beyond 2 gigabytes."); + + if (newSize < Length) + throw new Exception("ByteBuffer: cannot truncate buffer."); + + byte[] newBuffer = new byte[newSize]; + System.Buffer.BlockCopy(_buffer, 0, newBuffer, newSize - Length, Length); + _buffer = newBuffer; + InitBuffer(); + } + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + public override Span Span => _buffer; + public override ReadOnlySpan ReadOnlySpan => _buffer; + public override Memory Memory => _buffer; + public override ReadOnlyMemory ReadOnlyMemory => _buffer; +#endif + + private void InitBuffer() + { + Length = _buffer.Length; +#if !ENABLE_SPAN_T + Buffer = _buffer; +#endif + } + } + + /// + /// Class to mimic Java's ByteBuffer which is used heavily in Flatbuffers. + /// + public class ByteBuffer + { + private ByteBufferAllocator _buffer; + private int _pos; // Must track start of the buffer. + + public ByteBuffer(ByteBufferAllocator allocator, int position) + { + _buffer = allocator; + _pos = position; + } + + public ByteBuffer(int size) : this(new byte[size]) { } + + public ByteBuffer(byte[] buffer) : this(buffer, 0) { } + + public ByteBuffer(byte[] buffer, int pos) + { + _buffer = new ByteArrayAllocator(buffer); + _pos = pos; + } + + public int Position + { + get { return _pos; } + set { _pos = value; } + } + + public int Length { get { return _buffer.Length; } } + + public void Reset() + { + _pos = 0; + } + + // Create a new ByteBuffer on the same underlying data. + // The new ByteBuffer's position will be same as this buffer's. + public ByteBuffer Duplicate() + { + return new ByteBuffer(_buffer, Position); + } + + // Increases the size of the ByteBuffer, and copies the old data towards + // the end of the new buffer. + public void GrowFront(int newSize) + { + _buffer.GrowFront(newSize); + } + + public byte[] ToArray(int pos, int len) + { + return ToArray(pos, len); + } + + /// + /// A lookup of type sizes. Used instead of Marshal.SizeOf() which has additional + /// overhead, but also is compatible with generic functions for simplified code. + /// + private static Dictionary genericSizes = new Dictionary() + { + { typeof(bool), sizeof(bool) }, + { typeof(float), sizeof(float) }, + { typeof(double), sizeof(double) }, + { typeof(sbyte), sizeof(sbyte) }, + { typeof(byte), sizeof(byte) }, + { typeof(short), sizeof(short) }, + { typeof(ushort), sizeof(ushort) }, + { typeof(int), sizeof(int) }, + { typeof(uint), sizeof(uint) }, + { typeof(ulong), sizeof(ulong) }, + { typeof(long), sizeof(long) }, + }; + + /// + /// Get the wire-size (in bytes) of a type supported by flatbuffers. + /// + /// The type to get the wire size of + /// + public static int SizeOf() + { + return genericSizes[typeof(T)]; + } + + /// + /// Checks if the Type provided is supported as scalar value + /// + /// The Type to check + /// True if the type is a scalar type that is supported, falsed otherwise + public static bool IsSupportedType() + { + return genericSizes.ContainsKey(typeof(T)); + } + + /// + /// Get the wire-size (in bytes) of an typed array + /// + /// The type of the array + /// The array to get the size of + /// The number of bytes the array takes on wire + public static int ArraySize(T[] x) + { + return SizeOf() * x.Length; + } + + /// + /// Get the wire-size (in bytes) of an typed array segment, taking only the + /// range specified by into account. + /// + /// The type of the array + /// The array segment to get the size of + /// The number of bytes the array segment takes on wire + public static int ArraySize(ArraySegment x) + { + return SizeOf() * x.Count; + } + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + public static int ArraySize(Span x) + { + return SizeOf() * x.Length; + } +#endif + + // Get a portion of the buffer casted into an array of type T, given + // the buffer position and length. +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + public T[] ToArray(int pos, int len) + where T : struct + { + AssertOffsetAndLength(pos, len); + return MemoryMarshal.Cast(_buffer.ReadOnlySpan.Slice(pos)).Slice(0, len).ToArray(); + } +#else + public T[] ToArray(int pos, int len) + where T : struct + { + AssertOffsetAndLength(pos, len); + T[] arr = new T[len]; + Buffer.BlockCopy(_buffer.Buffer, pos, arr, 0, ArraySize(arr)); + return arr; + } +#endif + + public byte[] ToSizedArray() + { + return ToArray(Position, Length - Position); + } + + public byte[] ToFullArray() + { + return ToArray(0, Length); + } + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + public ReadOnlyMemory ToReadOnlyMemory(int pos, int len) + { + return _buffer.ReadOnlyMemory.Slice(pos, len); + } + + public Memory ToMemory(int pos, int len) + { + return _buffer.Memory.Slice(pos, len); + } + + public Span ToSpan(int pos, int len) + { + return _buffer.Span.Slice(pos, len); + } +#else + public ArraySegment ToArraySegment(int pos, int len) + { + return new ArraySegment(_buffer.Buffer, pos, len); + } + + public MemoryStream ToMemoryStream(int pos, int len) + { + return new MemoryStream(_buffer.Buffer, pos, len); + } +#endif + +#if !UNSAFE_BYTEBUFFER + // A conversion union where all the members are overlapping. This allows to reinterpret the bytes of one type + // as another, without additional copies. + [StructLayout(LayoutKind.Explicit)] + struct ConversionUnion + { + [FieldOffset(0)] public int intValue; + [FieldOffset(0)] public float floatValue; + } +#endif // !UNSAFE_BYTEBUFFER + + // Helper functions for the unsafe version. + static public ushort ReverseBytes(ushort input) + { + return (ushort)(((input & 0x00FFU) << 8) | + ((input & 0xFF00U) >> 8)); + } + static public uint ReverseBytes(uint input) + { + return ((input & 0x000000FFU) << 24) | + ((input & 0x0000FF00U) << 8) | + ((input & 0x00FF0000U) >> 8) | + ((input & 0xFF000000U) >> 24); + } + static public ulong ReverseBytes(ulong input) + { + return (((input & 0x00000000000000FFUL) << 56) | + ((input & 0x000000000000FF00UL) << 40) | + ((input & 0x0000000000FF0000UL) << 24) | + ((input & 0x00000000FF000000UL) << 8) | + ((input & 0x000000FF00000000UL) >> 8) | + ((input & 0x0000FF0000000000UL) >> 24) | + ((input & 0x00FF000000000000UL) >> 40) | + ((input & 0xFF00000000000000UL) >> 56)); + } + +#if !UNSAFE_BYTEBUFFER && (!ENABLE_SPAN_T || !NETSTANDARD2_1) + // Helper functions for the safe (but slower) version. + protected void WriteLittleEndian(int offset, int count, ulong data) + { + if (BitConverter.IsLittleEndian) + { + for (int i = 0; i < count; i++) + { + _buffer.Buffer[offset + i] = (byte)(data >> i * 8); + } + } + else + { + for (int i = 0; i < count; i++) + { + _buffer.Buffer[offset + count - 1 - i] = (byte)(data >> i * 8); + } + } + } + + protected ulong ReadLittleEndian(int offset, int count) + { + AssertOffsetAndLength(offset, count); + ulong r = 0; + if (BitConverter.IsLittleEndian) + { + for (int i = 0; i < count; i++) + { + r |= (ulong)_buffer.Buffer[offset + i] << i * 8; + } + } + else + { + for (int i = 0; i < count; i++) + { + r |= (ulong)_buffer.Buffer[offset + count - 1 - i] << i * 8; + } + } + return r; + } +#elif ENABLE_SPAN_T && NETSTANDARD2_1 + protected void WriteLittleEndian(int offset, int count, ulong data) + { + if (BitConverter.IsLittleEndian) + { + for (int i = 0; i < count; i++) + { + _buffer.Span[offset + i] = (byte)(data >> i * 8); + } + } + else + { + for (int i = 0; i < count; i++) + { + _buffer.Span[offset + count - 1 - i] = (byte)(data >> i * 8); + } + } + } + + protected ulong ReadLittleEndian(int offset, int count) + { + AssertOffsetAndLength(offset, count); + ulong r = 0; + if (BitConverter.IsLittleEndian) + { + for (int i = 0; i < count; i++) + { + r |= (ulong)_buffer.Span[offset + i] << i * 8; + } + } + else + { + for (int i = 0; i < count; i++) + { + r |= (ulong)_buffer.Span[offset + count - 1 - i] << i * 8; + } + } + return r; + } +#endif + + private void AssertOffsetAndLength(int offset, int length) + { +#if !BYTEBUFFER_NO_BOUNDS_CHECK + if (offset < 0 || + offset > _buffer.Length - length) + throw new ArgumentOutOfRangeException(); +#endif + } + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + + public void PutSbyte(int offset, sbyte value) + { + AssertOffsetAndLength(offset, sizeof(sbyte)); + _buffer.Span[offset] = (byte)value; + } + + public void PutByte(int offset, byte value) + { + AssertOffsetAndLength(offset, sizeof(byte)); + _buffer.Span[offset] = value; + } + + public void PutByte(int offset, byte value, int count) + { + AssertOffsetAndLength(offset, sizeof(byte) * count); + Span span = _buffer.Span.Slice(offset, count); + for (var i = 0; i < span.Length; ++i) + span[i] = value; + } +#else + public void PutSbyte(int offset, sbyte value) + { + AssertOffsetAndLength(offset, sizeof(sbyte)); + _buffer.Buffer[offset] = (byte)value; + } + + public void PutByte(int offset, byte value) + { + AssertOffsetAndLength(offset, sizeof(byte)); + _buffer.Buffer[offset] = value; + } + + public void PutByte(int offset, byte value, int count) + { + AssertOffsetAndLength(offset, sizeof(byte) * count); + for (var i = 0; i < count; ++i) + _buffer.Buffer[offset + i] = value; + } +#endif + + // this method exists in order to conform with Java ByteBuffer standards + public void Put(int offset, byte value) + { + PutByte(offset, value); + } + +#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER + public unsafe void PutStringUTF8(int offset, string value) + { + AssertOffsetAndLength(offset, value.Length); + fixed (char* s = value) + { + fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.Span)) + { + Encoding.UTF8.GetBytes(s, value.Length, buffer + offset, Length - offset); + } + } + } +#elif ENABLE_SPAN_T && NETSTANDARD2_1 + public void PutStringUTF8(int offset, string value) + { + AssertOffsetAndLength(offset, value.Length); + Encoding.UTF8.GetBytes(value.AsSpan().Slice(0, value.Length), + _buffer.Span.Slice(offset)); + } +#else + public void PutStringUTF8(int offset, string value) + { + AssertOffsetAndLength(offset, value.Length); + Encoding.UTF8.GetBytes(value, 0, value.Length, + _buffer.Buffer, offset); + } +#endif + +#if UNSAFE_BYTEBUFFER + // Unsafe but more efficient versions of Put*. + public void PutShort(int offset, short value) + { + PutUshort(offset, (ushort)value); + } + + public unsafe void PutUshort(int offset, ushort value) + { + AssertOffsetAndLength(offset, sizeof(ushort)); +#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER + Span span = _buffer.Span.Slice(offset); + BinaryPrimitives.WriteUInt16LittleEndian(span, value); +#else + fixed (byte* ptr = _buffer.Buffer) + { + *(ushort*)(ptr + offset) = BitConverter.IsLittleEndian + ? value + : ReverseBytes(value); + } +#endif + } + + public void PutInt(int offset, int value) + { + PutUint(offset, (uint)value); + } + + public unsafe void PutUint(int offset, uint value) + { + AssertOffsetAndLength(offset, sizeof(uint)); +#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER + Span span = _buffer.Span.Slice(offset); + BinaryPrimitives.WriteUInt32LittleEndian(span, value); +#else + fixed (byte* ptr = _buffer.Buffer) + { + *(uint*)(ptr + offset) = BitConverter.IsLittleEndian + ? value + : ReverseBytes(value); + } +#endif + } + + public unsafe void PutLong(int offset, long value) + { + PutUlong(offset, (ulong)value); + } + + public unsafe void PutUlong(int offset, ulong value) + { + AssertOffsetAndLength(offset, sizeof(ulong)); +#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER + Span span = _buffer.Span.Slice(offset); + BinaryPrimitives.WriteUInt64LittleEndian(span, value); +#else + fixed (byte* ptr = _buffer.Buffer) + { + *(ulong*)(ptr + offset) = BitConverter.IsLittleEndian + ? value + : ReverseBytes(value); + } +#endif + } + + public unsafe void PutFloat(int offset, float value) + { + AssertOffsetAndLength(offset, sizeof(float)); +#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER + fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span)) +#else + fixed (byte* ptr = _buffer.Buffer) +#endif + { + if (BitConverter.IsLittleEndian) + { + *(float*)(ptr + offset) = value; + } + else + { + *(uint*)(ptr + offset) = ReverseBytes(*(uint*)(&value)); + } + } + } + + public unsafe void PutDouble(int offset, double value) + { + AssertOffsetAndLength(offset, sizeof(double)); +#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER + fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span)) +#else + fixed (byte* ptr = _buffer.Buffer) +#endif + { + if (BitConverter.IsLittleEndian) + { + *(double*)(ptr + offset) = value; + } + else + { + *(ulong*)(ptr + offset) = ReverseBytes(*(ulong*)(&value)); + } + } + } +#else // !UNSAFE_BYTEBUFFER + // Slower versions of Put* for when unsafe code is not allowed. + public void PutShort(int offset, short value) + { + AssertOffsetAndLength(offset, sizeof(short)); + WriteLittleEndian(offset, sizeof(short), (ulong)value); + } + + public void PutUshort(int offset, ushort value) + { + AssertOffsetAndLength(offset, sizeof(ushort)); + WriteLittleEndian(offset, sizeof(ushort), (ulong)value); + } + + public void PutInt(int offset, int value) + { + AssertOffsetAndLength(offset, sizeof(int)); + WriteLittleEndian(offset, sizeof(int), (ulong)value); + } + + public void PutUint(int offset, uint value) + { + AssertOffsetAndLength(offset, sizeof(uint)); + WriteLittleEndian(offset, sizeof(uint), (ulong)value); + } + + public void PutLong(int offset, long value) + { + AssertOffsetAndLength(offset, sizeof(long)); + WriteLittleEndian(offset, sizeof(long), (ulong)value); + } + + public void PutUlong(int offset, ulong value) + { + AssertOffsetAndLength(offset, sizeof(ulong)); + WriteLittleEndian(offset, sizeof(ulong), value); + } + + public void PutFloat(int offset, float value) + { + AssertOffsetAndLength(offset, sizeof(float)); + // TODO(derekbailey): use BitConvert.SingleToInt32Bits() whenever flatbuffers upgrades to a .NET version + // that contains it. + ConversionUnion union; + union.intValue = 0; + union.floatValue = value; + WriteLittleEndian(offset, sizeof(float), (ulong)union.intValue); + } + + public void PutDouble(int offset, double value) + { + AssertOffsetAndLength(offset, sizeof(double)); + WriteLittleEndian(offset, sizeof(double), (ulong)BitConverter.DoubleToInt64Bits(value)); + } + +#endif // UNSAFE_BYTEBUFFER + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + public sbyte GetSbyte(int index) + { + AssertOffsetAndLength(index, sizeof(sbyte)); + return (sbyte)_buffer.ReadOnlySpan[index]; + } + + public byte Get(int index) + { + AssertOffsetAndLength(index, sizeof(byte)); + return _buffer.ReadOnlySpan[index]; + } +#else + public sbyte GetSbyte(int index) + { + AssertOffsetAndLength(index, sizeof(sbyte)); + return (sbyte)_buffer.Buffer[index]; + } + + public byte Get(int index) + { + AssertOffsetAndLength(index, sizeof(byte)); + return _buffer.Buffer[index]; + } +#endif + +#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER + public unsafe string GetStringUTF8(int startPos, int len) + { + fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan.Slice(startPos))) + { + return Encoding.UTF8.GetString(buffer, len); + } + } +#elif ENABLE_SPAN_T && NETSTANDARD2_1 + public string GetStringUTF8(int startPos, int len) + { + return Encoding.UTF8.GetString(_buffer.Span.Slice(startPos, len)); + } +#else + public string GetStringUTF8(int startPos, int len) + { + return Encoding.UTF8.GetString(_buffer.Buffer, startPos, len); + } +#endif + +#if UNSAFE_BYTEBUFFER + // Unsafe but more efficient versions of Get*. + public short GetShort(int offset) + { + return (short)GetUshort(offset); + } + + public unsafe ushort GetUshort(int offset) + { + AssertOffsetAndLength(offset, sizeof(ushort)); +#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER + ReadOnlySpan span = _buffer.ReadOnlySpan.Slice(offset); + return BinaryPrimitives.ReadUInt16LittleEndian(span); +#else + fixed (byte* ptr = _buffer.Buffer) + { + return BitConverter.IsLittleEndian + ? *(ushort*)(ptr + offset) + : ReverseBytes(*(ushort*)(ptr + offset)); + } +#endif + } + + public int GetInt(int offset) + { + return (int)GetUint(offset); + } + + public unsafe uint GetUint(int offset) + { + AssertOffsetAndLength(offset, sizeof(uint)); +#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER + ReadOnlySpan span = _buffer.ReadOnlySpan.Slice(offset); + return BinaryPrimitives.ReadUInt32LittleEndian(span); +#else + fixed (byte* ptr = _buffer.Buffer) + { + return BitConverter.IsLittleEndian + ? *(uint*)(ptr + offset) + : ReverseBytes(*(uint*)(ptr + offset)); + } +#endif + } + + public long GetLong(int offset) + { + return (long)GetUlong(offset); + } + + public unsafe ulong GetUlong(int offset) + { + AssertOffsetAndLength(offset, sizeof(ulong)); +#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER + ReadOnlySpan span = _buffer.ReadOnlySpan.Slice(offset); + return BinaryPrimitives.ReadUInt64LittleEndian(span); +#else + fixed (byte* ptr = _buffer.Buffer) + { + return BitConverter.IsLittleEndian + ? *(ulong*)(ptr + offset) + : ReverseBytes(*(ulong*)(ptr + offset)); + } +#endif + } + + public unsafe float GetFloat(int offset) + { + AssertOffsetAndLength(offset, sizeof(float)); +#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER + fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan)) +#else + fixed (byte* ptr = _buffer.Buffer) +#endif + { + if (BitConverter.IsLittleEndian) + { + return *(float*)(ptr + offset); + } + else + { + uint uvalue = ReverseBytes(*(uint*)(ptr + offset)); + return *(float*)(&uvalue); + } + } + } + + public unsafe double GetDouble(int offset) + { + AssertOffsetAndLength(offset, sizeof(double)); +#if ENABLE_SPAN_T // && UNSAFE_BYTEBUFFER + fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan)) +#else + fixed (byte* ptr = _buffer.Buffer) +#endif + { + if (BitConverter.IsLittleEndian) + { + return *(double*)(ptr + offset); + } + else + { + ulong uvalue = ReverseBytes(*(ulong*)(ptr + offset)); + return *(double*)(&uvalue); + } + } + } +#else // !UNSAFE_BYTEBUFFER + // Slower versions of Get* for when unsafe code is not allowed. + public short GetShort(int index) + { + return (short)ReadLittleEndian(index, sizeof(short)); + } + + public ushort GetUshort(int index) + { + return (ushort)ReadLittleEndian(index, sizeof(ushort)); + } + + public int GetInt(int index) + { + return (int)ReadLittleEndian(index, sizeof(int)); + } + + public uint GetUint(int index) + { + return (uint)ReadLittleEndian(index, sizeof(uint)); + } + + public long GetLong(int index) + { + return (long)ReadLittleEndian(index, sizeof(long)); + } + + public ulong GetUlong(int index) + { + return ReadLittleEndian(index, sizeof(ulong)); + } + + public float GetFloat(int index) + { + // TODO(derekbailey): use BitConvert.Int32BitsToSingle() whenever flatbuffers upgrades to a .NET version + // that contains it. + ConversionUnion union; + union.floatValue = 0; + union.intValue = (int)ReadLittleEndian(index, sizeof(float)); + return union.floatValue; + } + + public double GetDouble(int index) + { + return BitConverter.Int64BitsToDouble((long)ReadLittleEndian(index, sizeof(double))); + } +#endif // UNSAFE_BYTEBUFFER + + /// + /// Copies an array of type T into this buffer, ending at the given + /// offset into this buffer. The starting offset is calculated based on the length + /// of the array and is the value returned. + /// + /// The type of the input data (must be a struct) + /// The offset into this buffer where the copy will end + /// The array to copy data from + /// The 'start' location of this buffer now, after the copy completed + public int Put(int offset, T[] x) + where T : struct + { + if (x == null) + { + throw new ArgumentNullException("Cannot put a null array"); + } + + return Put(offset, new ArraySegment(x)); + } + + /// + /// Copies an array segment of type T into this buffer, ending at the + /// given offset into this buffer. The starting offset is calculated + /// based on the count of the array segment and is the value returned. + /// + /// The type of the input data (must be a struct) + /// + /// The offset into this buffer where the copy + /// will end + /// The array segment to copy data from + /// The 'start' location of this buffer now, after the copy + /// completed + public int Put(int offset, ArraySegment x) + where T : struct + { + if (x.Equals(default(ArraySegment))) + { + throw new ArgumentNullException("Cannot put a uninitialized array segment"); + } + + if (x.Count == 0) + { + throw new ArgumentException("Cannot put an empty array"); + } + + if (!IsSupportedType()) + { + throw new ArgumentException("Cannot put an array of type " + + typeof(T) + " into this buffer"); + } + + if (BitConverter.IsLittleEndian) + { + int numBytes = ByteBuffer.ArraySize(x); + offset -= numBytes; + AssertOffsetAndLength(offset, numBytes); + // if we are LE, just do a block copy +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + MemoryMarshal.Cast(x).CopyTo(_buffer.Span.Slice(offset, numBytes)); +#else + var srcOffset = ByteBuffer.SizeOf() * x.Offset; + Buffer.BlockCopy(x.Array, srcOffset, _buffer.Buffer, offset, numBytes); +#endif + } + else + { + throw new NotImplementedException("Big Endian Support not implemented yet " + + "for putting typed arrays"); + // if we are BE, we have to swap each element by itself + //for(int i = x.Length - 1; i >= 0; i--) + //{ + // todo: low priority, but need to genericize the Put() functions + //} + } + return offset; + } + + /// + /// Copies an array segment of type T into this buffer, ending at the + /// given offset into this buffer. The starting offset is calculated + /// based on the count of the array segment and is the value returned. + /// + /// The type of the input data (must be a struct) + /// + /// The offset into this buffer where the copy + /// will end + /// The pointer to copy data from + /// The number of bytes to copy + /// The 'start' location of this buffer now, after the copy + /// completed + public int Put(int offset, IntPtr ptr, int sizeInBytes) + where T : struct + { + if (ptr == IntPtr.Zero) + { + throw new ArgumentNullException("Cannot add a null pointer"); + } + + if(sizeInBytes <= 0) + { + throw new ArgumentException("Cannot put an empty array"); + } + + if (!IsSupportedType()) + { + throw new ArgumentException("Cannot put an array of type " + + typeof(T) + " into this buffer"); + } + + if (BitConverter.IsLittleEndian) + { + offset -= sizeInBytes; + AssertOffsetAndLength(offset, sizeInBytes); + // if we are LE, just do a block copy +#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER + unsafe + { + var span = new Span(ptr.ToPointer(), sizeInBytes); + span.CopyTo(_buffer.Span.Slice(offset, sizeInBytes)); + } +#else + Marshal.Copy(ptr, _buffer.Buffer, offset, sizeInBytes); +#endif + } + else + { + throw new NotImplementedException("Big Endian Support not implemented yet " + + "for putting typed arrays"); + // if we are BE, we have to swap each element by itself + //for(int i = x.Length - 1; i >= 0; i--) + //{ + // todo: low priority, but need to genericize the Put() functions + //} + } + return offset; + } + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + public int Put(int offset, Span x) + where T : struct + { + if (x.Length == 0) + { + throw new ArgumentException("Cannot put an empty array"); + } + + if (!IsSupportedType()) + { + throw new ArgumentException("Cannot put an array of type " + + typeof(T) + " into this buffer"); + } + + if (BitConverter.IsLittleEndian) + { + int numBytes = ByteBuffer.ArraySize(x); + offset -= numBytes; + AssertOffsetAndLength(offset, numBytes); + // if we are LE, just do a block copy + MemoryMarshal.Cast(x).CopyTo(_buffer.Span.Slice(offset, numBytes)); + } + else + { + throw new NotImplementedException("Big Endian Support not implemented yet " + + "for putting typed arrays"); + // if we are BE, we have to swap each element by itself + //for(int i = x.Length - 1; i >= 0; i--) + //{ + // todo: low priority, but need to genericize the Put() functions + //} + } + return offset; + } +#endif + } +} diff --git a/Source/ThirdParty/FlatBuffers/ByteBufferUtil.cs b/Source/ThirdParty/FlatBuffers/ByteBufferUtil.cs new file mode 100644 index 000000000..a5f2fb942 --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/ByteBufferUtil.cs @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; + +namespace Google.FlatBuffers +{ + /// + /// Class that collects utility functions around `ByteBuffer`. + /// + public class ByteBufferUtil + { + // Extract the size prefix from a `ByteBuffer`. + public static int GetSizePrefix(ByteBuffer bb) { + return bb.GetInt(bb.Position); + } + + // Create a duplicate of a size-prefixed `ByteBuffer` that has its position + // advanced just past the size prefix. + public static ByteBuffer RemoveSizePrefix(ByteBuffer bb) { + ByteBuffer s = bb.Duplicate(); + s.Position += FlatBufferConstants.SizePrefixLength; + return s; + } + } +} diff --git a/Source/ThirdParty/FlatBuffers/FlatBufferBuilder.cs b/Source/ThirdParty/FlatBuffers/FlatBufferBuilder.cs new file mode 100644 index 000000000..e08db847b --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/FlatBufferBuilder.cs @@ -0,0 +1,1023 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +using System; +using System.Collections.Generic; +using System.Text; + +/// @file +/// @addtogroup flatbuffers_csharp_api +/// @{ + +namespace Google.FlatBuffers +{ + /// + /// Responsible for building up and accessing a FlatBuffer formatted byte + /// array (via ByteBuffer). + /// + public class FlatBufferBuilder + { + private int _space; + private ByteBuffer _bb; + private int _minAlign = 1; + + // The vtable for the current table (if _vtableSize >= 0) + private int[] _vtable = new int[16]; + // The size of the vtable. -1 indicates no vtable + private int _vtableSize = -1; + // Starting offset of the current struct/table. + private int _objectStart; + // List of offsets of all vtables. + private int[] _vtables = new int[16]; + // Number of entries in `vtables` in use. + private int _numVtables = 0; + // For the current vector being built. + private int _vectorNumElems = 0; + + // For CreateSharedString + private Dictionary _sharedStringMap = null; + + /// + /// Create a FlatBufferBuilder with a given initial size. + /// + /// + /// The initial size to use for the internal buffer. + /// + public FlatBufferBuilder(int initialSize) + { + if (initialSize <= 0) + throw new ArgumentOutOfRangeException("initialSize", + initialSize, "Must be greater than zero"); + _space = initialSize; + _bb = new ByteBuffer(initialSize); + } + + /// + /// Create a FlatBufferBuilder backed by the pased in ByteBuffer + /// + /// The ByteBuffer to write to + public FlatBufferBuilder(ByteBuffer buffer) + { + _bb = buffer; + _space = buffer.Length; + buffer.Reset(); + } + + /// + /// Reset the FlatBufferBuilder by purging all data that it holds. + /// + public void Clear() + { + _space = _bb.Length; + _bb.Reset(); + _minAlign = 1; + while (_vtableSize > 0) _vtable[--_vtableSize] = 0; + _vtableSize = -1; + _objectStart = 0; + _numVtables = 0; + _vectorNumElems = 0; + if (_sharedStringMap != null) + { + _sharedStringMap.Clear(); + } + } + + /// + /// Gets and sets a Boolean to disable the optimization when serializing + /// default values to a Table. + /// + /// In order to save space, fields that are set to their default value + /// don't get serialized into the buffer. + /// + public bool ForceDefaults { get; set; } + + /// @cond FLATBUFFERS_INTERNAL + + public int Offset { get { return _bb.Length - _space; } } + + public void Pad(int size) + { + _bb.PutByte(_space -= size, 0, size); + } + + // Doubles the size of the ByteBuffer, and copies the old data towards + // the end of the new buffer (since we build the buffer backwards). + void GrowBuffer() + { + _bb.GrowFront(_bb.Length << 1); + } + + // Prepare to write an element of `size` after `additional_bytes` + // have been written, e.g. if you write a string, you need to align + // such the int length field is aligned to SIZEOF_INT, and the string + // data follows it directly. + // If all you need to do is align, `additional_bytes` will be 0. + public void Prep(int size, int additionalBytes) + { + // Track the biggest thing we've ever aligned to. + if (size > _minAlign) + _minAlign = size; + // Find the amount of alignment needed such that `size` is properly + // aligned after `additional_bytes` + var alignSize = + ((~((int)_bb.Length - _space + additionalBytes)) + 1) & + (size - 1); + // Reallocate the buffer if needed. + while (_space < alignSize + size + additionalBytes) + { + var oldBufSize = (int)_bb.Length; + GrowBuffer(); + _space += (int)_bb.Length - oldBufSize; + + } + if (alignSize > 0) + Pad(alignSize); + } + + public void PutBool(bool x) + { + _bb.PutByte(_space -= sizeof(byte), (byte)(x ? 1 : 0)); + } + + public void PutSbyte(sbyte x) + { + _bb.PutSbyte(_space -= sizeof(sbyte), x); + } + + public void PutByte(byte x) + { + _bb.PutByte(_space -= sizeof(byte), x); + } + + public void PutShort(short x) + { + _bb.PutShort(_space -= sizeof(short), x); + } + + public void PutUshort(ushort x) + { + _bb.PutUshort(_space -= sizeof(ushort), x); + } + + public void PutInt(int x) + { + _bb.PutInt(_space -= sizeof(int), x); + } + + public void PutUint(uint x) + { + _bb.PutUint(_space -= sizeof(uint), x); + } + + public void PutLong(long x) + { + _bb.PutLong(_space -= sizeof(long), x); + } + + public void PutUlong(ulong x) + { + _bb.PutUlong(_space -= sizeof(ulong), x); + } + + public void PutFloat(float x) + { + _bb.PutFloat(_space -= sizeof(float), x); + } + + /// + /// Puts an array of type T into this builder at the + /// current offset + /// + /// The type of the input data + /// The array to copy data from + public void Put(T[] x) + where T : struct + { + _space = _bb.Put(_space, x); + } + + /// + /// Puts an array of type T into this builder at the + /// current offset + /// + /// The type of the input data + /// The array segment to copy data from + public void Put(ArraySegment x) + where T : struct + { + _space = _bb.Put(_space, x); + } + + /// + /// Puts data of type T into this builder at the + /// current offset + /// + /// The type of the input data + /// The pointer to copy data from + /// The length of the data in bytes + public void Put(IntPtr ptr, int sizeInBytes) + where T : struct + { + _space = _bb.Put(_space, ptr, sizeInBytes); + } + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + /// + /// Puts a span of type T into this builder at the + /// current offset + /// + /// The type of the input data + /// The span to copy data from + public void Put(Span x) + where T : struct + { + _space = _bb.Put(_space, x); + } +#endif + + public void PutDouble(double x) + { + _bb.PutDouble(_space -= sizeof(double), x); + } + /// @endcond + + /// + /// Add a `bool` to the buffer (aligns the data and grows if necessary). + /// + /// The `bool` to add to the buffer. + public void AddBool(bool x) { Prep(sizeof(byte), 0); PutBool(x); } + + /// + /// Add a `sbyte` to the buffer (aligns the data and grows if necessary). + /// + /// The `sbyte` to add to the buffer. + public void AddSbyte(sbyte x) { Prep(sizeof(sbyte), 0); PutSbyte(x); } + + /// + /// Add a `byte` to the buffer (aligns the data and grows if necessary). + /// + /// The `byte` to add to the buffer. + public void AddByte(byte x) { Prep(sizeof(byte), 0); PutByte(x); } + + /// + /// Add a `short` to the buffer (aligns the data and grows if necessary). + /// + /// The `short` to add to the buffer. + public void AddShort(short x) { Prep(sizeof(short), 0); PutShort(x); } + + /// + /// Add an `ushort` to the buffer (aligns the data and grows if necessary). + /// + /// The `ushort` to add to the buffer. + public void AddUshort(ushort x) { Prep(sizeof(ushort), 0); PutUshort(x); } + + /// + /// Add an `int` to the buffer (aligns the data and grows if necessary). + /// + /// The `int` to add to the buffer. + public void AddInt(int x) { Prep(sizeof(int), 0); PutInt(x); } + + /// + /// Add an `uint` to the buffer (aligns the data and grows if necessary). + /// + /// The `uint` to add to the buffer. + public void AddUint(uint x) { Prep(sizeof(uint), 0); PutUint(x); } + + /// + /// Add a `long` to the buffer (aligns the data and grows if necessary). + /// + /// The `long` to add to the buffer. + public void AddLong(long x) { Prep(sizeof(long), 0); PutLong(x); } + + /// + /// Add an `ulong` to the buffer (aligns the data and grows if necessary). + /// + /// The `ulong` to add to the buffer. + public void AddUlong(ulong x) { Prep(sizeof(ulong), 0); PutUlong(x); } + + /// + /// Add a `float` to the buffer (aligns the data and grows if necessary). + /// + /// The `float` to add to the buffer. + public void AddFloat(float x) { Prep(sizeof(float), 0); PutFloat(x); } + + /// + /// Add an array of type T to the buffer (aligns the data and grows if necessary). + /// + /// The type of the input data + /// The array to copy data from + public void Add(T[] x) + where T : struct + { + Add(new ArraySegment(x)); + } + + /// + /// Add an array of type T to the buffer (aligns the data and grows if necessary). + /// + /// The type of the input data + /// The array segment to copy data from + public void Add(ArraySegment x) + where T : struct + { + if (x == null) + { + throw new ArgumentNullException("Cannot add a null array"); + } + + if( x.Count == 0) + { + // don't do anything if the array is empty + return; + } + + if(!ByteBuffer.IsSupportedType()) + { + throw new ArgumentException("Cannot add this Type array to the builder"); + } + + int size = ByteBuffer.SizeOf(); + // Need to prep on size (for data alignment) and then we pass the + // rest of the length (minus 1) as additional bytes + Prep(size, size * (x.Count - 1)); + Put(x); + } + + /// + /// Adds the data of type T pointed to by the given pointer to the buffer (aligns the data and grows if necessary). + /// + /// The type of the input data + /// The pointer to copy data from + /// The data size in bytes + public void Add(IntPtr ptr, int sizeInBytes) + where T : struct + { + if(sizeInBytes == 0) + { + // don't do anything if the array is empty + return; + } + + if (ptr == IntPtr.Zero) + { + throw new ArgumentNullException("Cannot add a null pointer"); + } + + if(sizeInBytes < 0) + { + throw new ArgumentOutOfRangeException("sizeInBytes", "sizeInBytes cannot be negative"); + } + + if(!ByteBuffer.IsSupportedType()) + { + throw new ArgumentException("Cannot add this Type array to the builder"); + } + + int size = ByteBuffer.SizeOf(); + if((sizeInBytes % size) != 0) + { + throw new ArgumentException("The given size in bytes " + sizeInBytes + " doesn't match the element size of T ( " + size + ")", "sizeInBytes"); + } + + // Need to prep on size (for data alignment) and then we pass the + // rest of the length (minus 1) as additional bytes + Prep(size, sizeInBytes - size); + Put(ptr, sizeInBytes); + } + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + /// + /// Add a span of type T to the buffer (aligns the data and grows if necessary). + /// + /// The type of the input data + /// The span to copy data from + public void Add(Span x) + where T : struct + { + if (!ByteBuffer.IsSupportedType()) + { + throw new ArgumentException("Cannot add this Type array to the builder"); + } + + int size = ByteBuffer.SizeOf(); + // Need to prep on size (for data alignment) and then we pass the + // rest of the length (minus 1) as additional bytes + Prep(size, size * (x.Length - 1)); + Put(x); + } +#endif + + /// + /// Add a `double` to the buffer (aligns the data and grows if necessary). + /// + /// The `double` to add to the buffer. + public void AddDouble(double x) { Prep(sizeof(double), 0); + PutDouble(x); } + + /// + /// Adds an offset, relative to where it will be written. + /// + /// The offset to add to the buffer. + public void AddOffset(int off) + { + Prep(sizeof(int), 0); // Ensure alignment is already done. + if (off > Offset) + throw new ArgumentException(); + + if (off != 0) + off = Offset - off + sizeof(int); + PutInt(off); + } + + /// @cond FLATBUFFERS_INTERNAL + public void StartVector(int elemSize, int count, int alignment) + { + NotNested(); + _vectorNumElems = count; + Prep(sizeof(int), elemSize * count); + Prep(alignment, elemSize * count); // Just in case alignment > int. + } + /// @endcond + + /// + /// Writes data necessary to finish a vector construction. + /// + public VectorOffset EndVector() + { + PutInt(_vectorNumElems); + return new VectorOffset(Offset); + } + + /// + /// Creates a vector of tables. + /// + /// Offsets of the tables. + public VectorOffset CreateVectorOfTables(Offset[] offsets) where T : struct + { + NotNested(); + StartVector(sizeof(int), offsets.Length, sizeof(int)); + for (int i = offsets.Length - 1; i >= 0; i--) AddOffset(offsets[i].Value); + return EndVector(); + } + + /// @cond FLATBUFFERS_INTENRAL + public void Nested(int obj) + { + // Structs are always stored inline, so need to be created right + // where they are used. You'll get this assert if you created it + // elsewhere. + if (obj != Offset) + throw new Exception( + "FlatBuffers: struct must be serialized inline."); + } + + public void NotNested() + { + // You should not be creating any other objects or strings/vectors + // while an object is being constructed + if (_vtableSize >= 0) + throw new Exception( + "FlatBuffers: object serialization must not be nested."); + } + + public void StartTable(int numfields) + { + if (numfields < 0) + throw new ArgumentOutOfRangeException("Flatbuffers: invalid numfields"); + + NotNested(); + + if (_vtable.Length < numfields) + _vtable = new int[numfields]; + + _vtableSize = numfields; + _objectStart = Offset; + } + + + // Set the current vtable at `voffset` to the current location in the + // buffer. + public void Slot(int voffset) + { + if (voffset >= _vtableSize) + throw new IndexOutOfRangeException("Flatbuffers: invalid voffset"); + + _vtable[voffset] = Offset; + } + + /// + /// Adds a Boolean to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddBool(int o, bool x, bool d) { if (ForceDefaults || x != d) { AddBool(x); Slot(o); } } + + /// + /// Adds a Boolean to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable boolean value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddBool(int o, bool? x) { if (x.HasValue) { AddBool(x.Value); Slot(o); } } + + + /// + /// Adds a SByte to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddSbyte(int o, sbyte x, sbyte d) { if (ForceDefaults || x != d) { AddSbyte(x); Slot(o); } } + + /// + /// Adds a SByte to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable sbyte value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddSbyte(int o, sbyte? x) { if (x.HasValue) { AddSbyte(x.Value); Slot(o); } } + + /// + /// Adds a Byte to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddByte(int o, byte x, byte d) { if (ForceDefaults || x != d) { AddByte(x); Slot(o); } } + + /// + /// Adds a Byte to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable byte value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddByte(int o, byte? x) { if (x.HasValue) { AddByte(x.Value); Slot(o); } } + + /// + /// Adds a Int16 to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddShort(int o, short x, int d) { if (ForceDefaults || x != d) { AddShort(x); Slot(o); } } + + /// + /// Adds a Int16 to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable int16 value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddShort(int o, short? x) { if (x.HasValue) { AddShort(x.Value); Slot(o); } } + + /// + /// Adds a UInt16 to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddUshort(int o, ushort x, ushort d) { if (ForceDefaults || x != d) { AddUshort(x); Slot(o); } } + + /// + /// Adds a Uint16 to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable uint16 value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddUshort(int o, ushort? x) { if (x.HasValue) { AddUshort(x.Value); Slot(o); } } + + /// + /// Adds an Int32 to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddInt(int o, int x, int d) { if (ForceDefaults || x != d) { AddInt(x); Slot(o); } } + + /// + /// Adds a Int32 to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable int32 value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddInt(int o, int? x) { if (x.HasValue) { AddInt(x.Value); Slot(o); } } + + /// + /// Adds a UInt32 to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddUint(int o, uint x, uint d) { if (ForceDefaults || x != d) { AddUint(x); Slot(o); } } + + /// + /// Adds a UInt32 to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable uint32 value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddUint(int o, uint? x) { if (x.HasValue) { AddUint(x.Value); Slot(o); } } + + /// + /// Adds an Int64 to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddLong(int o, long x, long d) { if (ForceDefaults || x != d) { AddLong(x); Slot(o); } } + + /// + /// Adds a Int64 to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable int64 value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddLong(int o, long? x) { if (x.HasValue) { AddLong(x.Value); Slot(o); } } + + /// + /// Adds a UInt64 to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddUlong(int o, ulong x, ulong d) { if (ForceDefaults || x != d) { AddUlong(x); Slot(o); } } + + /// + /// Adds a UInt64 to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable int64 value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddUlong(int o, ulong? x) { if (x.HasValue) { AddUlong(x.Value); Slot(o); } } + + /// + /// Adds a Single to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddFloat(int o, float x, double d) { if (ForceDefaults || x != d) { AddFloat(x); Slot(o); } } + + /// + /// Adds a Single to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable single value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddFloat(int o, float? x) { if (x.HasValue) { AddFloat(x.Value); Slot(o); } } + + /// + /// Adds a Double to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// and is false, the value will be skipped. + /// The default value to compare the value against + public void AddDouble(int o, double x, double d) { if (ForceDefaults || x != d) { AddDouble(x); Slot(o); } } + + /// + /// Adds a Double to the Table at index `o` in its vtable using the nullable value `x` + /// + /// The index into the vtable + /// The nullable double value to put into the buffer. If it doesn't have a value + /// it will skip writing to the buffer. + public void AddDouble(int o, double? x) { if (x.HasValue) { AddDouble(x.Value); Slot(o); } } + + /// + /// Adds a buffer offset to the Table at index `o` in its vtable using the value `x` and default `d` + /// + /// The index into the vtable + /// The value to put into the buffer. If the value is equal to the default + /// the value will be skipped. + /// The default value to compare the value against + public void AddOffset(int o, int x, int d) { if (x != d) { AddOffset(x); Slot(o); } } + /// @endcond + + /// + /// Encode the string `s` in the buffer using UTF-8. + /// + /// The string to encode. + /// + /// The offset in the buffer where the encoded string starts. + /// + public StringOffset CreateString(string s) + { + if (s == null) + { + return new StringOffset(0); + } + NotNested(); + AddByte(0); + var utf8StringLen = Encoding.UTF8.GetByteCount(s); + StartVector(1, utf8StringLen, 1); + _bb.PutStringUTF8(_space -= utf8StringLen, s); + return new StringOffset(EndVector().Value); + } + + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + /// + /// Creates a string in the buffer from a Span containing + /// a UTF8 string. + /// + /// the UTF8 string to add to the buffer + /// + /// The offset in the buffer where the encoded string starts. + /// + public StringOffset CreateUTF8String(Span chars) + { + NotNested(); + AddByte(0); + var utf8StringLen = chars.Length; + StartVector(1, utf8StringLen, 1); + _space = _bb.Put(_space, chars); + return new StringOffset(EndVector().Value); + } +#endif + + /// + /// Store a string in the buffer, which can contain any binary data. + /// If a string with this exact contents has already been serialized before, + /// instead simply returns the offset of the existing string. + /// + /// The string to encode. + /// + /// The offset in the buffer where the encoded string starts. + /// + public StringOffset CreateSharedString(string s) + { + if (s == null) + { + return new StringOffset(0); + } + + if (_sharedStringMap == null) + { + _sharedStringMap = new Dictionary(); + } + + if (_sharedStringMap.ContainsKey(s)) + { + return _sharedStringMap[s]; + } + + var stringOffset = CreateString(s); + _sharedStringMap.Add(s, stringOffset); + return stringOffset; + } + + /// @cond FLATBUFFERS_INTERNAL + // Structs are stored inline, so nothing additional is being added. + // `d` is always 0. + public void AddStruct(int voffset, int x, int d) + { + if (x != d) + { + Nested(x); + Slot(voffset); + } + } + + public int EndTable() + { + if (_vtableSize < 0) + throw new InvalidOperationException( + "Flatbuffers: calling EndTable without a StartTable"); + + AddInt((int)0); + var vtableloc = Offset; + // Write out the current vtable. + int i = _vtableSize - 1; + // Trim trailing zeroes. + for (; i >= 0 && _vtable[i] == 0; i--) {} + int trimmedSize = i + 1; + for (; i >= 0 ; i--) { + // Offset relative to the start of the table. + short off = (short)(_vtable[i] != 0 + ? vtableloc - _vtable[i] + : 0); + AddShort(off); + + // clear out written entry + _vtable[i] = 0; + } + + const int standardFields = 2; // The fields below: + AddShort((short)(vtableloc - _objectStart)); + AddShort((short)((trimmedSize + standardFields) * + sizeof(short))); + + // Search for an existing vtable that matches the current one. + int existingVtable = 0; + for (i = 0; i < _numVtables; i++) { + int vt1 = _bb.Length - _vtables[i]; + int vt2 = _space; + short len = _bb.GetShort(vt1); + if (len == _bb.GetShort(vt2)) { + for (int j = sizeof(short); j < len; j += sizeof(short)) { + if (_bb.GetShort(vt1 + j) != _bb.GetShort(vt2 + j)) { + goto endLoop; + } + } + existingVtable = _vtables[i]; + break; + } + + endLoop: { } + } + + if (existingVtable != 0) { + // Found a match: + // Remove the current vtable. + _space = _bb.Length - vtableloc; + // Point table to existing vtable. + _bb.PutInt(_space, existingVtable - vtableloc); + } else { + // No match: + // Add the location of the current vtable to the list of + // vtables. + if (_numVtables == _vtables.Length) + { + // Arrays.CopyOf(vtables num_vtables * 2); + var newvtables = new int[ _numVtables * 2]; + Array.Copy(_vtables, newvtables, _vtables.Length); + + _vtables = newvtables; + }; + _vtables[_numVtables++] = Offset; + // Point table to current vtable. + _bb.PutInt(_bb.Length - vtableloc, Offset - vtableloc); + } + + _vtableSize = -1; + return vtableloc; + } + + // This checks a required field has been set in a given table that has + // just been constructed. + public void Required(int table, int field) + { + int table_start = _bb.Length - table; + int vtable_start = table_start - _bb.GetInt(table_start); + bool ok = _bb.GetShort(vtable_start + field) != 0; + // If this fails, the caller will show what field needs to be set. + if (!ok) + throw new InvalidOperationException("FlatBuffers: field " + field + + " must be set"); + } + /// @endcond + + /// + /// Finalize a buffer, pointing to the given `root_table`. + /// + /// + /// An offset to be added to the buffer. + /// + /// + /// Whether to prefix the size to the buffer. + /// + protected void Finish(int rootTable, bool sizePrefix) + { + Prep(_minAlign, sizeof(int) + (sizePrefix ? sizeof(int) : 0)); + AddOffset(rootTable); + if (sizePrefix) { + AddInt(_bb.Length - _space); + } + _bb.Position = _space; + } + + /// + /// Finalize a buffer, pointing to the given `root_table`. + /// + /// + /// An offset to be added to the buffer. + /// + public void Finish(int rootTable) + { + Finish(rootTable, false); + } + + /// + /// Finalize a buffer, pointing to the given `root_table`, with the size prefixed. + /// + /// + /// An offset to be added to the buffer. + /// + public void FinishSizePrefixed(int rootTable) + { + Finish(rootTable, true); + } + + /// + /// Get the ByteBuffer representing the FlatBuffer. + /// + /// + /// This is typically only called after you call `Finish()`. + /// The actual data starts at the ByteBuffer's current position, + /// not necessarily at `0`. + /// + /// + /// Returns the ByteBuffer for this FlatBuffer. + /// + public ByteBuffer DataBuffer { get { return _bb; } } + + /// + /// A utility function to copy and return the ByteBuffer data as a + /// `byte[]`. + /// + /// + /// A full copy of the FlatBuffer data. + /// + public byte[] SizedByteArray() + { + return _bb.ToSizedArray(); + } + + /// + /// Finalize a buffer, pointing to the given `rootTable`. + /// + /// + /// An offset to be added to the buffer. + /// + /// + /// A FlatBuffer file identifier to be added to the buffer before + /// `root_table`. + /// + /// + /// Whether to prefix the size to the buffer. + /// + protected void Finish(int rootTable, string fileIdentifier, bool sizePrefix) + { + Prep(_minAlign, sizeof(int) + (sizePrefix ? sizeof(int) : 0) + + FlatBufferConstants.FileIdentifierLength); + if (fileIdentifier.Length != + FlatBufferConstants.FileIdentifierLength) + throw new ArgumentException( + "FlatBuffers: file identifier must be length " + + FlatBufferConstants.FileIdentifierLength, + "fileIdentifier"); + for (int i = FlatBufferConstants.FileIdentifierLength - 1; i >= 0; + i--) + { + AddByte((byte)fileIdentifier[i]); + } + Finish(rootTable, sizePrefix); + } + + /// + /// Finalize a buffer, pointing to the given `rootTable`. + /// + /// + /// An offset to be added to the buffer. + /// + /// + /// A FlatBuffer file identifier to be added to the buffer before + /// `root_table`. + /// + public void Finish(int rootTable, string fileIdentifier) + { + Finish(rootTable, fileIdentifier, false); + } + + /// + /// Finalize a buffer, pointing to the given `rootTable`, with the size prefixed. + /// + /// + /// An offset to be added to the buffer. + /// + /// + /// A FlatBuffer file identifier to be added to the buffer before + /// `root_table`. + /// + public void FinishSizePrefixed(int rootTable, string fileIdentifier) + { + Finish(rootTable, fileIdentifier, true); + } + } +} + +/// @} diff --git a/Source/ThirdParty/FlatBuffers/FlatBufferConstants.cs b/Source/ThirdParty/FlatBuffers/FlatBufferConstants.cs new file mode 100644 index 000000000..51a5d6e88 --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/FlatBufferConstants.cs @@ -0,0 +1,37 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Google.FlatBuffers +{ + public static class FlatBufferConstants + { + public const int FileIdentifierLength = 4; + public const int SizePrefixLength = 4; + /** A version identifier to force a compile error if someone + accidentally tries to build generated code with a runtime of + two mismatched version. Versions need to always match, as + the runtime and generated code are modified in sync. + Changes to the C# implementation need to be sure to change + the version here and in the code generator on every possible + incompatible change */ + public static void FLATBUFFERS_23_5_26() {} + } +} diff --git a/Source/ThirdParty/FlatBuffers/FlatBufferVerify.cs b/Source/ThirdParty/FlatBuffers/FlatBufferVerify.cs new file mode 100644 index 000000000..15064e8a5 --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/FlatBufferVerify.cs @@ -0,0 +1,822 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Reflection;using System.Collections.Generic; +using System.IO; + +namespace Google.FlatBuffers +{ + + /// + /// The Class of the Verifier Options + /// + public class Options + { + public const int DEFAULT_MAX_DEPTH = 64; + public const int DEFAULT_MAX_TABLES = 1000000; + + private int max_depth = 0; + private int max_tables = 0; + private bool string_end_check = false; + private bool alignment_check = false; + + public Options() + { + max_depth = DEFAULT_MAX_DEPTH; + max_tables = DEFAULT_MAX_TABLES; + string_end_check = true; + alignment_check = true; + } + + public Options(int maxDepth, int maxTables, bool stringEndCheck, bool alignmentCheck) + { + max_depth = maxDepth; + max_tables = maxTables; + string_end_check = stringEndCheck; + alignment_check = alignmentCheck; + } + /// Maximum depth of nested tables allowed in a valid flatbuffer. + public int maxDepth + { + get { return max_depth; } + set { max_depth = value; } + } + /// Maximum number of tables allowed in a valid flatbuffer. + public int maxTables + { + get { return max_tables; } + set { max_tables = value; } + } + /// Check that string contains its null terminator + public bool stringEndCheck + { + get { return string_end_check; } + set { string_end_check = value; } + } + /// Check alignment of elements + public bool alignmentCheck + { + get { return alignment_check; } + set { alignment_check = value; } + } + } + + public struct checkElementStruct + { + public bool elementValid; + public uint elementOffset; + } + + public delegate bool VerifyTableAction(Verifier verifier, uint tablePos); + public delegate bool VerifyUnionAction(Verifier verifier, byte typeId, uint tablePos); + + /// + /// The Main Class of the FlatBuffer Verifier + /// + public class Verifier + { + private ByteBuffer verifier_buffer = null; + private Options verifier_options = null; + private int depth_cnt = 0; + private int num_tables_cnt = 0; + + public const int SIZE_BYTE = 1; + public const int SIZE_INT = 4; + public const int SIZE_U_OFFSET = 4; + public const int SIZE_S_OFFSET = 4; + public const int SIZE_V_OFFSET = 2; + public const int SIZE_PREFIX_LENGTH = FlatBufferConstants.SizePrefixLength; // default size = 4 + public const int FLATBUFFERS_MAX_BUFFER_SIZE = System.Int32.MaxValue; // default size = 2147483647 + public const int FILE_IDENTIFIER_LENGTH = FlatBufferConstants.FileIdentifierLength; // default size = 4 + + /// The Base Constructor of the Verifier object + public Verifier() + { + // Verifier buffer + verifier_buffer = null; + // Verifier settings + verifier_options = null; + // Depth counter + depth_cnt = 0; + // Tables counter + num_tables_cnt = 0; + } + + /// The Constructor of the Verifier object with input parameters: ByteBuffer and/or Options + /// Input flat byte buffer defined as ByteBuffer type + /// Options object with settings for the coniguration the Verifier + public Verifier(ByteBuffer buf, Options options = null) + { + verifier_buffer = buf; + verifier_options = options ?? new Options(); + depth_cnt = 0; + num_tables_cnt = 0; + } + + /// Bytes Buffer for Verify + public ByteBuffer Buf + { + get { return verifier_buffer; } + set { verifier_buffer = value; } + } + /// Options of the Verifier + public Options options + { + get { return verifier_options; } + set { verifier_options = value; } + } + /// Counter of tables depth in a tested flatbuffer + public int depth + { + get { return depth_cnt; } + set { depth_cnt = value; } + } + /// Counter of tables in a tested flatbuffer + public int numTables + { + get { return num_tables_cnt; } + set { num_tables_cnt = value; } + } + + + /// Method set maximum tables depth of valid structure + /// Specify Value of the maximum depth of the structure + public Verifier SetMaxDepth(int value) + { + verifier_options.maxDepth = value; + return this; + } + /// Specify maximum number of tables in structure + /// Specify Value of the maximum number of the tables in the structure + public Verifier SetMaxTables(int value) + { + verifier_options.maxTables = value; + return this; + } + /// Enable/disable buffer content alignment check + /// Value of the State for buffer content alignment check (Enable = true) + public Verifier SetAlignmentCheck(bool value) + { + verifier_options.alignmentCheck = value; + return this; + } + /// Enable/disable checking of string termination '0' character + /// Value of the option for string termination '0' character check (Enable = true) + public Verifier SetStringCheck(bool value) + { + verifier_options.stringEndCheck = value; + return this; + } + + /// Check if there is identifier in buffer + /// Input flat byte buffer defined as ByteBuffer type + /// Start position of data in the Byte Buffer + /// Identifier for the Byte Buffer + /// Return True when the Byte Buffer Identifier is present + private bool BufferHasIdentifier(ByteBuffer buf, uint startPos, string identifier) + { + if (identifier.Length != FILE_IDENTIFIER_LENGTH) + { + throw new ArgumentException("FlatBuffers: file identifier must be length" + Convert.ToString(FILE_IDENTIFIER_LENGTH)); + } + for (int i = 0; i < FILE_IDENTIFIER_LENGTH; i++) + { + if ((sbyte)identifier[i] != verifier_buffer.GetSbyte(Convert.ToInt32(SIZE_S_OFFSET + i + startPos))) + { + return false; + } + } + + return true; + } + + /// Get UOffsetT from buffer at given position - it must be verified before read + /// Input flat byte buffer defined as ByteBuffer type + /// Position of data in the Byte Buffer + /// Return the UOffset Value (Unsigned Integer type - 4 bytes) in pos + private uint ReadUOffsetT(ByteBuffer buf, uint pos) + { + return buf.GetUint(Convert.ToInt32(pos)); + } + /// Get SOffsetT from buffer at given position - it must be verified before read + /// Input flat byte buffer defined as ByteBuffer type + /// Position of data in the Byte Buffer + /// Return the SOffset Value (Signed Integer type - 4 bytes) in pos + private int ReadSOffsetT(ByteBuffer buf, int pos) + { + return buf.GetInt(pos); + } + /// Get VOffsetT from buffer at given position - it must be verified before read + /// Input flat byte buffer defined as ByteBuffer type + /// Position of data in the Byte Buffer + /// Return the VOffset Value (Short type - 2 bytes) in pos + private short ReadVOffsetT(ByteBuffer buf, int pos) + { + return buf.GetShort(pos); + } + + /// Get table data area relative offset from vtable. Result is relative to table start + /// Fields which are deprecated are ignored by checking against the vtable's length. + /// Position of data in the Byte Buffer + /// offset of value in the Table + /// Return the relative VOffset Value (Short type - 2 bytes) in calculated offset + private short GetVRelOffset(int pos, short vtableOffset) + { + short VOffset = 0; + // Used try/catch because pos typa as int 32bit + try + { + // First, get vtable offset + short vtable = Convert.ToInt16(pos - ReadSOffsetT(verifier_buffer, pos)); + // Check that offset points to vtable area (is smaller than vtable size) + if (vtableOffset < ReadVOffsetT(verifier_buffer, vtable)) + { + // Now, we can read offset value - TODO check this value against size of table data + VOffset = ReadVOffsetT(verifier_buffer, vtable + vtableOffset); + } + else + { + VOffset = 0; + } + } + catch (Exception e) + { + Console.WriteLine("Exception: {0}", e); + return VOffset; + } + return VOffset; + + } + /// Get table data area absolute offset from vtable. Result is the absolute buffer offset. + /// The result value offset cannot be '0' (pointing to itself) so after validation this method returnes '0' + /// value as a marker for missing optional entry + /// Table Position value in the Byte Buffer + /// offset value in the Table + /// Return the absolute UOffset Value + private uint GetVOffset(uint tablePos, short vtableOffset) + { + uint UOffset = 0; + // First, get vtable relative offset + short relPos = GetVRelOffset(Convert.ToInt32(tablePos), vtableOffset); + if (relPos != 0) + { + // Calculate offset based on table postion + UOffset = Convert.ToUInt32(tablePos + relPos); + } + else + { + UOffset = 0; + } + return UOffset; + } + + /// Check flatbuffer complexity (tables depth, elements counter and so on) + /// If complexity is too high function returns false as verification error + private bool CheckComplexity() + { + return ((depth <= options.maxDepth) && (numTables <= options.maxTables)); + } + + /// Check alignment of element. + /// Return True when alignment of the element is correct + private bool CheckAlignment(uint element, ulong align) + { + return (((element & (align - 1)) == 0) || (!options.alignmentCheck)); + } + + /// Check if element is valid in buffer area. + /// Value defines the offset/position to element + /// Size of element + /// Return True when Element is correct + private bool CheckElement(uint pos, ulong elementSize) + { + return ((elementSize < Convert.ToUInt64(verifier_buffer.Length)) && (pos <= (Convert.ToUInt32(verifier_buffer.Length) - elementSize))); + } + /// Check if element is a valid scalar. + /// Value defines the offset to scalar + /// Size of element + /// Return True when Scalar Element is correct + private bool CheckScalar(uint pos, ulong elementSize) + { + return ((CheckAlignment(pos, elementSize)) && (CheckElement(pos, elementSize))); + } + /// Check offset. It is a scalar with size of UOffsetT. + private bool CheckOffset(uint offset) + { + return (CheckScalar(offset, SIZE_U_OFFSET)); + } + + private checkElementStruct CheckVectorOrString(uint pos, ulong elementSize) + { + var result = new checkElementStruct + { + elementValid = false, + elementOffset = 0 + }; + + uint vectorPos = pos; + // Check we can read the vector/string size field (it is of uoffset size) + if (!CheckScalar(vectorPos, SIZE_U_OFFSET)) + { + // result.elementValid = false; result.elementOffset = 0; + return result; + } + // Check the whole array. If this is a string, the byte past the array + // must be 0. + uint size = ReadUOffsetT(verifier_buffer, vectorPos); + ulong max_elements = (FLATBUFFERS_MAX_BUFFER_SIZE / elementSize); + if (size >= max_elements) + { + // Protect against byte_size overflowing. + // result.elementValid = false; result.elementOffset = 0; + return result; + } + + uint bytes_size = SIZE_U_OFFSET + (Convert.ToUInt32(elementSize) * size); + uint buffer_end_pos = vectorPos + bytes_size; + result.elementValid = CheckElement(vectorPos, bytes_size); + result.elementOffset = buffer_end_pos; + return (result); + } + + /// Verify a string at given position. + private bool CheckString(uint pos) + { + var result = CheckVectorOrString(pos, SIZE_BYTE); + if (options.stringEndCheck) + { + result.elementValid = result.elementValid && CheckScalar(result.elementOffset, 1); // Must have terminator + result.elementValid = result.elementValid && (verifier_buffer.GetSbyte(Convert.ToInt32(result.elementOffset)) == 0); // Terminating byte must be 0. + } + return result.elementValid; + } + + /// Verify the vector of elements of given size + private bool CheckVector(uint pos, ulong elementSize) + { + var result = CheckVectorOrString(pos, elementSize); + return result.elementValid; + } + /// Verify table content using structure dependent generated function + private bool CheckTable(uint tablePos, VerifyTableAction verifyAction) + { + return verifyAction(this, tablePos); + } + + /// String check wrapper function to be used in vector of strings check + private bool CheckStringFunc(Verifier verifier, uint pos) + { + return verifier.CheckString(pos); + } + + /// Check vector of objects. Use generated object verification function + private bool CheckVectorOfObjects(uint pos, VerifyTableAction verifyAction) + { + if (!CheckVector(pos, SIZE_U_OFFSET)) + { + return false; + } + uint size = ReadUOffsetT(verifier_buffer, pos); + // Vector data starts just after vector size/length + uint vecStart = pos + SIZE_U_OFFSET; + uint vecOff = 0; + // Iterate offsets and verify referenced objects + for (uint i = 0; i < size; i++) + { + vecOff = vecStart + (i * SIZE_U_OFFSET); + if (!CheckIndirectOffset(vecOff)) + { + return false; + } + uint objOffset = GetIndirectOffset(vecOff); + if (!verifyAction(this, objOffset)) + { + return false; + } + } + return true; + } + + /// Check if the offset referenced by offsetPos is the valid offset pointing to buffer + // offsetPos - offset to offset data + private bool CheckIndirectOffset(uint pos) + { + // Check the input offset is valid + if(!CheckScalar(pos, SIZE_U_OFFSET)) + { + return false; + } + // Get indirect offset + uint offset = ReadUOffsetT(verifier_buffer, pos); + // May not point to itself neither wrap around (buffers are max 2GB) + if ((offset == 0) || (offset >= FLATBUFFERS_MAX_BUFFER_SIZE)) + { + return false; + } + // Must be inside the buffer + return CheckElement(pos + offset, 1); + } + + /// Check flatbuffer content using generated object verification function + private bool CheckBufferFromStart(string identifier, uint startPos, VerifyTableAction verifyAction) + { + if ((identifier != null) && + (identifier.Length == 0) && + ((verifier_buffer.Length < (SIZE_U_OFFSET + FILE_IDENTIFIER_LENGTH)) || (!BufferHasIdentifier(verifier_buffer, startPos, identifier)))) + { + return false; + } + if(!CheckIndirectOffset(startPos)) + { + return false; + } + uint offset = GetIndirectOffset(startPos); + return CheckTable(offset, verifyAction); // && GetComputedSize() + } + + /// Get indirect offset. It is an offset referenced by offset Pos + private uint GetIndirectOffset(uint pos) + { + // Get indirect offset referenced by offsetPos + uint offset = pos + ReadUOffsetT(verifier_buffer, pos); + return offset; + } + + /// Verify beginning of table + /// Position in the Table + /// Return True when the verification of the beginning of the table is passed + // (this method is used internally by generated verification functions) + public bool VerifyTableStart(uint tablePos) + { + // Starting new table verification increases complexity of structure + depth_cnt++; + num_tables_cnt++; + + if (!CheckScalar(tablePos, SIZE_S_OFFSET)) + { + return false; + } + uint vtable = (uint)(tablePos - ReadSOffsetT(verifier_buffer, Convert.ToInt32(tablePos))); + return ((CheckComplexity()) && (CheckScalar(vtable, SIZE_V_OFFSET)) && (CheckAlignment(Convert.ToUInt32(ReadVOffsetT(verifier_buffer, Convert.ToInt32(vtable))), SIZE_V_OFFSET)) && (CheckElement(vtable, Convert.ToUInt64(ReadVOffsetT(verifier_buffer, Convert.ToInt32(vtable)))))); + } + + /// Verify end of table. In practice, this function does not check buffer but handles + /// verification statistics update + // (this method is used internally by generated verification functions) + public bool VerifyTableEnd(uint tablePos) + { + depth--; + return true; + } + + /// Verifiy static/inlined data area field + /// Position in the Table + /// Offset to the static/inlined data element + /// Size of the element + /// Alignment bool value + /// Required Value when the offset == 0 + /// Return True when the verification of the static/inlined data element is passed + // (this method is used internally by generated verification functions) + public bool VerifyField(uint tablePos, short offsetId, ulong elementSize, ulong align, bool required) + { + uint offset = GetVOffset(tablePos, offsetId); + if (offset != 0) + { + return ((CheckAlignment(offset, align)) && (CheckElement(offset, elementSize))); + } + return !required; // it is OK if field is not required + } + + /// Verify string + /// Position in the Table + /// Offset to the String element + /// Required Value when the offset == 0 + /// Return True when the verification of the String is passed + // (this method is used internally by generated verification functions) + public bool VerifyString(uint tablePos, short vOffset, bool required) + { + var offset = GetVOffset(tablePos, vOffset); + if (offset == 0) + { + return !required; + } + if (!CheckIndirectOffset(offset)) + { + return false; + } + var strOffset = GetIndirectOffset(offset); + return CheckString(strOffset); + } + + /// Verify vector of fixed size structures and scalars + /// Position in the Table + /// Offset to the Vector of Data + /// Size of the element + /// Required Value when the offset == 0 + /// Return True when the verification of the Vector of Data passed + // (this method is used internally by generated verification functions) + public bool VerifyVectorOfData(uint tablePos, short vOffset, ulong elementSize, bool required) + { + var offset = GetVOffset(tablePos, vOffset); + if (offset == 0) + { + return !required; + } + if (!CheckIndirectOffset(offset)) + { + return false; + } + var vecOffset = GetIndirectOffset(offset); + return CheckVector(vecOffset, elementSize); + } + + /// Verify array of strings + /// Position in the Table + /// Offset to the Vector of String + /// Required Value when the offset == 0 + /// Return True when the verification of the Vector of String passed + // (this method is used internally by generated verification functions) + public bool VerifyVectorOfStrings(uint tablePos, short offsetId, bool required) + { + var offset = GetVOffset(tablePos, offsetId); + if (offset == 0) + { + return !required; + } + if (!CheckIndirectOffset(offset)) + { + return false; + } + var vecOffset = GetIndirectOffset(offset); + return CheckVectorOfObjects(vecOffset, CheckStringFunc); + } + + /// Verify vector of tables (objects). Tables are verified using generated verifyObjFunc + /// Position in the Table + /// Offset to the Vector of Table + /// Method used to the verification Table + /// Required Value when the offset == 0 + /// Return True when the verification of the Vector of Table passed + // (this method is used internally by generated verification functions) + public bool VerifyVectorOfTables(uint tablePos, short offsetId, VerifyTableAction verifyAction, bool required) + { + var offset = GetVOffset(tablePos, offsetId); + if (offset == 0) + { + return !required; + } + if (!CheckIndirectOffset(offset)) + { + return false; + } + var vecOffset = GetIndirectOffset(offset); + return CheckVectorOfObjects(vecOffset, verifyAction); + } + + /// Verify table object using generated verification function. + /// Position in the Table + /// Offset to the Table + /// Method used to the verification Table + /// Required Value when the offset == 0 + /// Return True when the verification of the Table passed + // (this method is used internally by generated verification functions) + public bool VerifyTable(uint tablePos, short offsetId, VerifyTableAction verifyAction, bool required) + { + var offset = GetVOffset(tablePos, offsetId); + if (offset == 0) + { + return !required; + } + if (!CheckIndirectOffset(offset)) + { + return false; + } + var tabOffset = GetIndirectOffset(offset); + return CheckTable(tabOffset, verifyAction); + } + + /// Verify nested buffer object. When verifyObjFunc is provided, it is used to verify object structure. + /// Position in the Table + /// Offset to the Table + /// Method used to the verification Table + /// Required Value when the offset == 0 + // (this method is used internally by generated verification functions) + public bool VerifyNestedBuffer(uint tablePos, short offsetId, VerifyTableAction verifyAction, bool required) + { + var offset = GetVOffset(tablePos, offsetId); + if (offset == 0) + { + return !required; + } + uint vecOffset = GetIndirectOffset(offset); + if (!CheckVector(vecOffset, SIZE_BYTE)) + { + return false; + } + if (verifyAction != null) + { + var vecLength = ReadUOffsetT(verifier_buffer, vecOffset); + // Buffer begins after vector length + var vecStart = vecOffset + SIZE_U_OFFSET; + // Create and Copy nested buffer bytes from part of Verify Buffer + var nestedByteBuffer = new ByteBuffer(verifier_buffer.ToArray(Convert.ToInt32(vecStart), Convert.ToInt32(vecLength))); + var nestedVerifyier = new Verifier(nestedByteBuffer, options); + // There is no internal identifier - use empty one + if (!nestedVerifyier.CheckBufferFromStart("", 0, verifyAction)) + { + return false; + } + } + return true; + } + + /// Verifiy static/inlined data area at absolute offset + /// Position of static/inlined data area in the Byte Buffer + /// Size of the union data + /// Alignment bool value + /// Return True when the verification of the Union Data is passed + // (this method is used internally by generated verification functions) + public bool VerifyUnionData(uint pos, ulong elementSize, ulong align) + { + bool result = ((CheckAlignment(pos, align)) && (CheckElement(pos, elementSize))); + return result; + } + + /// Verify string referenced by absolute offset value + /// Position of Union String in the Byte Buffer + /// Return True when the verification of the Union String is passed + // (this method is used internally by generated verification functions) + public bool VerifyUnionString(uint pos) + { + bool result = CheckString(pos); + return result; + } + + /// Method verifies union object using generated verification function + /// Position in the Table + /// Offset in the Table + /// Offset to Element + /// Verification Method used for Union + /// Required Value when the offset == 0 + // (this method is used internally by generated verification functions) + public bool VerifyUnion(uint tablePos, short typeIdVOffset, short valueVOffset, VerifyUnionAction verifyAction, bool required) + { + // Check the union type index + var offset = GetVOffset(tablePos, typeIdVOffset); + if (offset == 0) + { + return !required; + } + if (!((CheckAlignment(offset, SIZE_BYTE)) && (CheckElement(offset, SIZE_BYTE)))) + { + return false; + } + // Check union data + offset = GetVOffset(tablePos, valueVOffset); + // Take type id + var typeId = verifier_buffer.Get(Convert.ToInt32(offset)); + if (offset == 0) + { + // When value data is not present, allow union verification function to deal with illegal offset + return verifyAction(this, typeId, Convert.ToUInt32(verifier_buffer.Length)); + } + if (!CheckIndirectOffset(offset)) + { + return false; + } + // Take value offset and validate union structure + uint unionOffset = GetIndirectOffset(offset); + return verifyAction(this, typeId, unionOffset); + } + + /// Verify vector of unions (objects). Unions are verified using generated verifyObjFunc + /// Position of the Table + /// Offset in the Table (Union type id) + /// Offset to vector of Data Stucture offset + /// Verification Method used for Union + /// Required Value when the offset == 0 + /// Return True when the verification of the Vector of Unions passed + // (this method is used internally by generated verification functions) + public bool VerifyVectorOfUnion(uint tablePos, short typeOffsetId, short offsetId, VerifyUnionAction verifyAction, bool required) + { + // type id offset must be valid + var offset = GetVOffset(tablePos, typeOffsetId); + if (offset == 0) + { + return !required; + } + if (!CheckIndirectOffset(offset)) + { + return false; + } + // Get type id table absolute offset + var typeIdVectorOffset = GetIndirectOffset(offset); + // values offset must be valid + offset = GetVOffset(tablePos, offsetId); + if (!CheckIndirectOffset(offset)) + { + return false; + } + var valueVectorOffset = GetIndirectOffset(offset); + // validate referenced vectors + if(!CheckVector(typeIdVectorOffset, SIZE_BYTE) || + !CheckVector(valueVectorOffset, SIZE_U_OFFSET)) + { + return false; + } + // Both vectors should have the same length + var typeIdVectorLength = ReadUOffsetT(verifier_buffer, typeIdVectorOffset); + var valueVectorLength = ReadUOffsetT(verifier_buffer, valueVectorOffset); + if (typeIdVectorLength != valueVectorLength) + { + return false; + } + // Verify each union from vectors + var typeIdStart = typeIdVectorOffset + SIZE_U_OFFSET; + var valueStart = valueVectorOffset + SIZE_U_OFFSET; + for (uint i = 0; i < typeIdVectorLength; i++) + { + // Get type id + byte typeId = verifier_buffer.Get(Convert.ToInt32(typeIdStart + i * SIZE_U_OFFSET)); + // get offset to vector item + uint off = valueStart + i * SIZE_U_OFFSET; + // Check the vector item has a proper offset + if (!CheckIndirectOffset(off)) + { + return false; + } + uint valueOffset = GetIndirectOffset(off); + // Verify object + if (!verifyAction(this, typeId, valueOffset)) + { + return false; + } + } + return true; + } + + // Method verifies flatbuffer data using generated Table verification function. + // The data buffer is already provided when creating [Verifier] object (see [NewVerifier]) + // + // - identifier - the expected identifier of buffer data. + // When empty identifier is provided the identifier validation is skipped. + // - sizePrefixed - this flag should be true when buffer is prefixed with content size + // - verifyObjFunc - function to be used for verification. This function is generated by compiler and included in each table definition file with name "Verify" + // + // Example: + // + // /* Verify Monster table. Ignore buffer name and assume buffer does not contain data length prefix */ + // isValid = verifier.verifyBuffer(bb, false, MonsterVerify) + // + // /* Verify Monster table. Buffer name is 'MONS' and contains data length prefix */ + // isValid = verifier.verifyBuffer("MONS", true, MonsterVerify) + /// Method verifies flatbuffer data using generated Table verification function + /// + /// The expected identifier of buffer data + /// Flag should be true when buffer is prefixed with content size + /// Function to be used for verification. This function is generated by compiler and included in each table definition file + /// Return True when verification of FlatBuffer passed + /// + /// Example 1. Verify Monster table. Ignore buffer name and assume buffer does not contain data length prefix + /// isValid = verifier.VerifyBuffer(bb, false, MonsterVerify) + /// Example 2. Verify Monster table. Buffer name is 'MONS' and contains data length prefix + /// isValid = verifier.VerifyBuffer("MONS", true, MonsterVerify) + /// + public bool VerifyBuffer(string identifier, bool sizePrefixed, VerifyTableAction verifyAction) + { + // Reset counters - starting verification from beginning + depth = 0; + numTables = 0; + + var start = (uint)(verifier_buffer.Position); + if (sizePrefixed) + { + start = (uint)(verifier_buffer.Position) + SIZE_PREFIX_LENGTH; + if(!CheckScalar((uint)(verifier_buffer.Position), SIZE_PREFIX_LENGTH)) + { + return false; + } + uint size = ReadUOffsetT(verifier_buffer, (uint)(verifier_buffer.Position)); + if (size != ((uint)(verifier_buffer.Length) - start)) + { + return false; + } + } + return CheckBufferFromStart(identifier, start, verifyAction); + } + } + +} diff --git a/Source/ThirdParty/FlatBuffers/FlatBuffers.net35.csproj b/Source/ThirdParty/FlatBuffers/FlatBuffers.net35.csproj new file mode 100644 index 000000000..9c64d006e --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/FlatBuffers.net35.csproj @@ -0,0 +1,57 @@ + + + + + Debug + AnyCPU + {28C00774-1E73-4A75-AD8F-844CD21A064D} + Library + Properties + FlatBuffers + FlatBuffers + v3.5 + 512 + + + true + full + false + bin\Debug\net35 + obj\Debug\net35 + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\net35 + obj\Release\net35 + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/ThirdParty/FlatBuffers/Google.FlatBuffers.csproj b/Source/ThirdParty/FlatBuffers/Google.FlatBuffers.csproj new file mode 100644 index 000000000..7d4fab0f8 --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/Google.FlatBuffers.csproj @@ -0,0 +1,46 @@ + + + + netstandard2.1;netstandard2.0;net46 + A cross-platform memory efficient serialization library + 23.5.26 + Google LLC + https://github.com/google/flatbuffers + https://github.com/google/flatbuffers + true + LICENSE + flatbuffers.png + Google;FlatBuffers;Serialization;Buffer;Binary;zero copy + Copyright 2022 Google LLC + true + snupkg + true + flatbuffers.snk + false + + + + $(DefineConstants);UNSAFE_BYTEBUFFER + true + + + $(DefineConstants);BYTEBUFFER_NO_BOUNDS_CHECK + + + $(DefineConstants);ENABLE_SPAN_T + + + + + + + + + + + + + + + + diff --git a/Source/ThirdParty/FlatBuffers/IFlatbufferObject.cs b/Source/ThirdParty/FlatBuffers/IFlatbufferObject.cs new file mode 100644 index 000000000..c1d106cd3 --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/IFlatbufferObject.cs @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Google.FlatBuffers +{ + /// + /// This is the base for both structs and tables. + /// + public interface IFlatbufferObject + { + void __init(int _i, ByteBuffer _bb); + + ByteBuffer ByteBuffer { get; } + } +} diff --git a/Source/ThirdParty/FlatBuffers/Offset.cs b/Source/ThirdParty/FlatBuffers/Offset.cs new file mode 100644 index 000000000..d2dd6281a --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/Offset.cs @@ -0,0 +1,48 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Google.FlatBuffers +{ + /// + /// Offset class for typesafe assignments. + /// + public struct Offset where T : struct + { + public int Value; + public Offset(int value) + { + Value = value; + } + } + + public struct StringOffset + { + public int Value; + public StringOffset(int value) + { + Value = value; + } + } + + public struct VectorOffset + { + public int Value; + public VectorOffset(int value) + { + Value = value; + } + } +} diff --git a/Source/ThirdParty/FlatBuffers/Struct.cs b/Source/ThirdParty/FlatBuffers/Struct.cs new file mode 100644 index 000000000..b4539bfd5 --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/Struct.cs @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Google.FlatBuffers +{ + /// + /// All structs in the generated code derive from this class, and add their own accessors. + /// + public struct Struct + { + public int bb_pos { get; private set; } + public ByteBuffer bb { get; private set; } + + // Re-init the internal state with an external buffer {@code ByteBuffer} and an offset within. + public Struct(int _i, ByteBuffer _bb) : this() + { + bb = _bb; + bb_pos = _i; + } + } +} diff --git a/Source/ThirdParty/FlatBuffers/Table.cs b/Source/ThirdParty/FlatBuffers/Table.cs new file mode 100644 index 000000000..2aaa86e99 --- /dev/null +++ b/Source/ThirdParty/FlatBuffers/Table.cs @@ -0,0 +1,212 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Text; +using System.Runtime.InteropServices; + +namespace Google.FlatBuffers +{ + /// + /// All tables in the generated code derive from this struct, and add their own accessors. + /// + public struct Table + { + public int bb_pos { get; private set; } + public ByteBuffer bb { get; private set; } + + public ByteBuffer ByteBuffer { get { return bb; } } + + // Re-init the internal state with an external buffer {@code ByteBuffer} and an offset within. + public Table(int _i, ByteBuffer _bb) : this() + { + bb = _bb; + bb_pos = _i; + } + + // Look up a field in the vtable, return an offset into the object, or 0 if the field is not + // present. + public int __offset(int vtableOffset) + { + int vtable = bb_pos - bb.GetInt(bb_pos); + return vtableOffset < bb.GetShort(vtable) ? (int)bb.GetShort(vtable + vtableOffset) : 0; + } + + public static int __offset(int vtableOffset, int offset, ByteBuffer bb) + { + int vtable = bb.Length - offset; + return (int)bb.GetShort(vtable + vtableOffset - bb.GetInt(vtable)) + vtable; + } + + // Retrieve the relative offset stored at "offset" + public int __indirect(int offset) + { + return offset + bb.GetInt(offset); + } + + public static int __indirect(int offset, ByteBuffer bb) + { + return offset + bb.GetInt(offset); + } + + // Create a .NET String from UTF-8 data stored inside the flatbuffer. + public string __string(int offset) + { + int stringOffset = bb.GetInt(offset); + if (stringOffset == 0) + return null; + + offset += stringOffset; + var len = bb.GetInt(offset); + var startPos = offset + sizeof(int); + return bb.GetStringUTF8(startPos, len); + } + + // Get the length of a vector whose offset is stored at "offset" in this object. + public int __vector_len(int offset) + { + offset += bb_pos; + offset += bb.GetInt(offset); + return bb.GetInt(offset); + } + + // Get the start of data of a vector whose offset is stored at "offset" in this object. + public int __vector(int offset) + { + offset += bb_pos; + return offset + bb.GetInt(offset) + sizeof(int); // data starts after the length + } + +#if ENABLE_SPAN_T && (UNSAFE_BYTEBUFFER || NETSTANDARD2_1) + // Get the data of a vector whoses offset is stored at "offset" in this object as an + // Spant<byte>. If the vector is not present in the ByteBuffer, + // then an empty span will be returned. + public Span __vector_as_span(int offset, int elementSize) where T : struct + { + if (!BitConverter.IsLittleEndian) + { + throw new NotSupportedException("Getting typed span on a Big Endian " + + "system is not support"); + } + + var o = this.__offset(offset); + if (0 == o) + { + return new Span(); + } + + var pos = this.__vector(o); + var len = this.__vector_len(o); + return MemoryMarshal.Cast(bb.ToSpan(pos, len * elementSize)); + } +#else + // Get the data of a vector whoses offset is stored at "offset" in this object as an + // ArraySegment<byte>. If the vector is not present in the ByteBuffer, + // then a null value will be returned. + public ArraySegment? __vector_as_arraysegment(int offset) + { + var o = this.__offset(offset); + if (0 == o) + { + return null; + } + + var pos = this.__vector(o); + var len = this.__vector_len(o); + return bb.ToArraySegment(pos, len); + } +#endif + + // Get the data of a vector whoses offset is stored at "offset" in this object as an + // T[]. If the vector is not present in the ByteBuffer, then a null value will be + // returned. + public T[] __vector_as_array(int offset) + where T : struct + { + if(!BitConverter.IsLittleEndian) + { + throw new NotSupportedException("Getting typed arrays on a Big Endian " + + "system is not support"); + } + + var o = this.__offset(offset); + if (0 == o) + { + return null; + } + + var pos = this.__vector(o); + var len = this.__vector_len(o); + return bb.ToArray(pos, len); + } + + // Initialize any Table-derived type to point to the union at the given offset. + public T __union(int offset) where T : struct, IFlatbufferObject + { + T t = new T(); + t.__init(__indirect(offset), bb); + return t; + } + + public static bool __has_identifier(ByteBuffer bb, string ident) + { + if (ident.Length != FlatBufferConstants.FileIdentifierLength) + throw new ArgumentException("FlatBuffers: file identifier must be length " + FlatBufferConstants.FileIdentifierLength, "ident"); + + for (var i = 0; i < FlatBufferConstants.FileIdentifierLength; i++) + { + if (ident[i] != (char)bb.Get(bb.Position + sizeof(int) + i)) return false; + } + + return true; + } + + // Compare strings in the ByteBuffer. + public static int CompareStrings(int offset_1, int offset_2, ByteBuffer bb) + { + offset_1 += bb.GetInt(offset_1); + offset_2 += bb.GetInt(offset_2); + var len_1 = bb.GetInt(offset_1); + var len_2 = bb.GetInt(offset_2); + var startPos_1 = offset_1 + sizeof(int); + var startPos_2 = offset_2 + sizeof(int); + var len = Math.Min(len_1, len_2); + for(int i = 0; i < len; i++) { + byte b1 = bb.Get(i + startPos_1); + byte b2 = bb.Get(i + startPos_2); + if (b1 != b2) + return b1 - b2; + } + return len_1 - len_2; + } + + // Compare string from the ByteBuffer with the string object + public static int CompareStrings(int offset_1, byte[] key, ByteBuffer bb) + { + offset_1 += bb.GetInt(offset_1); + var len_1 = bb.GetInt(offset_1); + var len_2 = key.Length; + var startPos_1 = offset_1 + sizeof(int); + var len = Math.Min(len_1, len_2); + for (int i = 0; i < len; i++) { + byte b = bb.Get(i + startPos_1); + if (b != key[i]) + return b - key[i]; + } + return len_1 - len_2; + } + } +} diff --git a/Source/ThirdParty/FlatBuffers/flatbuffers.png b/Source/ThirdParty/FlatBuffers/flatbuffers.png new file mode 100644 index 000000000..2c728f3b5 Binary files /dev/null and b/Source/ThirdParty/FlatBuffers/flatbuffers.png differ diff --git a/Source/ThirdParty/FlatBuffers/flatbuffers.snk b/Source/ThirdParty/FlatBuffers/flatbuffers.snk new file mode 100644 index 000000000..70a214680 Binary files /dev/null and b/Source/ThirdParty/FlatBuffers/flatbuffers.snk differ diff --git a/Source/ThirdParty/ThirdPartyModPatches.cs b/Source/ThirdParty/ThirdPartyModPatches.cs deleted file mode 100644 index 22214e8c6..000000000 --- a/Source/ThirdParty/ThirdPartyModPatches.cs +++ /dev/null @@ -1,33 +0,0 @@ -using BepInEx; -using BepInEx.Logging; -using StayInTarkov.UI; - -namespace StayInTarkov.ThirdParty -{ - public class ThirdPartyModPatches - { - private static BepInEx.Configuration.ConfigFile m_Config; - public static ManualLogSource Logger { get; private set; } - - static string ConfigSITOtherCategoryValue { get; } = "ThirdParty"; - - public static void Run(BepInEx.Configuration.ConfigFile config, BaseUnityPlugin plugin) - { - m_Config = config; - - if (Logger == null) - Logger = BepInEx.Logging.Logger.CreateLogSource("Third Party Mods"); - - var enabled = config.Bind(ConfigSITOtherCategoryValue, "Enable", true); - if (!enabled.Value) // if it is disabled. stop all Other Patches stuff. - { - Logger.LogInfo("Third Party patches have been disabled! Ignoring Patches."); - return; - } - - if (config.Bind(ConfigSITOtherCategoryValue, "EnableAdditionalAmmoUIDescriptions", true).Value) - new Ammo_CachedReadOnlyAttributes_Patch().Enable(); - - } - } -} diff --git a/Source/ThirdParty/Zlib.cs b/Source/ThirdParty/Zlib.cs index 0479f50cd..4d768ead5 100644 --- a/Source/ThirdParty/Zlib.cs +++ b/Source/ThirdParty/Zlib.cs @@ -1,7 +1,6 @@ using ComponentAce.Compression.Libs.zlib; using System.IO; using System.Text; -using System.Threading.Tasks; namespace StayInTarkov.ThirdParty { @@ -22,83 +21,72 @@ public enum ZlibCompression /// public static class Zlib { - // Level | CM/CI FLG - // ----- | --------- - // 1 | 78 01 - // 2 | 78 5E - // 3 | 78 5E - // 4 | 78 5E - // 5 | 78 5E - // 6 | 78 9C - // 7 | 78 DA - // 8 | 78 DA - // 9 | 78 DA - /// /// Check if the file is ZLib compressed /// - /// Data + /// Data /// If the file is Zlib compressed - public static bool IsCompressed(byte[] Data) + public static bool IsCompressed(byte[] data) { - // We need the first two bytes; - // First byte: Info (CM/CINFO) Header, should always be 0x78 - // Second byte: Flags (FLG) Header, should define our compression level. + if (data == null || data.Length < 3) + { + return false; + } - if (Data == null || Data.Length < 3 || Data[0] != 0x78) + // data[0]: Info (CM/CINFO) Header; must be 0x78 + if (data[0] != 0x78) { return false; } - switch (Data[1]) + // data[1]: Flags (FLG) Header; compression level. + switch (data[1]) { - case 0x01: // fastest - case 0x5E: // low - case 0x9C: // normal - case 0xDA: // max + case 0x01: // [0x78 0x01] level 0-2: fastest + case 0x5E: // [0x78 0x5E] level 3-4: low + case 0x9C: // [0x78 0x9C] level 5-6: normal + case 0xDA: // [0x78 0xDA] level 7-9: max return true; } return false; } - /// - /// Deflate data. - /// - - public static byte[] Compress(byte[] data, ZlibCompression level = ZlibCompression.Normal) + private static byte[] Run(byte[] data, ZlibCompression level) { - return SimpleZlib.CompressToBytes(Encoding.UTF8.GetString(data), 6, Encoding.UTF8); - } + // ZOutputStream.Close() flushes itself. + // ZOutputStream.Flush() flushes the target stream. + // It's fucking stupid, but whatever. + // -- Waffle.Lord, 2022-12-01 - //public static byte[] Compress(byte[] data, ZlibCompression level = ZlibCompression.Normal) - //{ - // var ms = new MemoryStream(); - // using (var zs = (level > ZlibCompression.Store) - // ? new ZOutputStream(ms, (int)level) - // : new ZOutputStream(ms)) - // { - // zs.Write(data, 0, data.Length); - // } + using (var ms = new MemoryStream()) + { + using (var zs = (level > ZlibCompression.Store) + ? new ZOutputStream(ms, (int)level) + : new ZOutputStream(ms)) + { + zs.Write(data, 0, data.Length); + } + // <-- zs flushes everything here - // var result = ms.ToArray(); - // ms.Close(); - // ms.Dispose(); - // ms = null; - // return result; - //} + return ms.ToArray(); + } + } /// - /// Inflate data. + /// Deflate data. /// - public static string Decompress(byte[] data) + public static byte[] Compress(byte[] data, ZlibCompression level) { - return SimpleZlib.Decompress(data); + return Run(data, level); } - public static byte[] DecompressToBytes(byte[] data) + /// + /// Inflate data. + /// + public static byte[] Decompress(byte[] data) { - return SimpleZlib.DecompressToBytes(data); + return Run(data, ZlibCompression.Store); } } } \ No newline at end of file diff --git a/Source/Tools/Autoraid.cs b/Source/Tools/Autoraid.cs new file mode 100644 index 000000000..889faf701 --- /dev/null +++ b/Source/Tools/Autoraid.cs @@ -0,0 +1,208 @@ +using BepInEx.Logging; +using Comfort.Common; +using EFT; +using EFT.Bots; +using EFT.UI; +using EFT.UI.Matchmaker; +using EFT.UI.Screens; +using EFT.UI.SessionEnd; +using JsonType; +using StayInTarkov.Coop.Components; +using StayInTarkov.Coop.Matchmaker; +using StayInTarkov.Coop.SITGameModes; +using StayInTarkov.Health; +using StayInTarkov.Networking; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.EventSystems; + +namespace StayInTarkov.Tools +{ + public class Autoraid : MonoBehaviour + { + public static bool Running = false; + + public void Start() + { + ScreenManager.Instance.OnScreenChanged += OnScreenChanged; + } + + public void OnDestroy() + { + Running = false; + } + + public static bool Requested() + { + return Environment.GetCommandLineArgs().FirstOrDefault(x => x == "-autoraid") != null; + } + + private void OnScreenChanged(EEftScreenType x) + { + if (x == EEftScreenType.MainMenu) + { + Running = true; + ScreenManager.Instance.OnScreenChanged -= OnScreenChanged; + + IEnumerator autoraid() + { + try + { + var i = 3; + + while (i-- > 0) + { + var screens = (Dictionary)ReflectionHelpers.GetFieldFromType(typeof(ScreenManager), "dictionary_0").GetValue(ScreenManager.Instance); + + { + yield return new WaitUntil(() => screens.ContainsKey(EEftScreenType.MainMenu) && screens[EEftScreenType.MainMenu].gameObject.activeSelf); + var playButton = new WaitForButton(screens[EEftScreenType.MainMenu], "_playButton"); + yield return playButton; + playButton.Button.OnClick.Invoke(); + } + + { + var pmcButton = new WaitForButton(screens[EEftScreenType.SelectRaidSide], "_pmcBigButton"); + yield return pmcButton; + pmcButton.Button.OnPointerClick(new PointerEventData(EventSystem.current) + { + button = PointerEventData.InputButton.Left + }); + var nextButton = new WaitForButton(screens[EEftScreenType.SelectRaidSide], "_nextButton"); + yield return nextButton; + nextButton.Button.OnClick.Invoke(); + } + + // remove as much non-determinism as we can + var app = StayInTarkovHelperConstants.GetMainApp(); + var botSettings = new BotControllerSettings(isScavWars: false, EBotAmount.Horde, EBossType.AsOnline); + var locationSettings = app.Session.LocationSettings; + var location = locationSettings.locations.Single(x => x.Value.Name == "Customs").Value; + location.BotSpawnTimeOffMin = 1; + location.BotSpawnTimeOffMax = 1; + location.BotSpawnPeriodCheck = 10; + location.BotStart = 0; + location.BotMax = 30; + foreach (var x in location.BossLocationSpawn) + { + x.BossChance = 100; + x.ForceSpawn = true; + x.IgnoreMaxBots = true; + } + var mmc = (MainMenuController)ReflectionHelpers.GetFieldFromType(typeof(TarkovApplication), "gclass1833_0").GetValue(app); + var raidSettings = (RaidSettings)ReflectionHelpers.GetFieldFromType(typeof(MainMenuController), "raidSettings_0").GetValue(mmc); + raidSettings.Apply(new RaidSettings( + side: ESideType.Pmc, + selectedDateTime: EDateTime.CURR, + raidMode: ERaidMode.Local, + timeAndWeatherSettings: new TimeAndWeatherSettings( + randomTime: false, + randomWeather: false, + cloudinessType: 0, + rainType: 0, + windSpeed: 0, + fogType: 0, + timeFlowType: 4, + hourOfDay: -1 + ), + locationSettings: locationSettings + ) + { + BotSettings = botSettings, + SelectedLocation = location + }); + var readyButton = new WaitForButton(screens[EEftScreenType.SelectLocation], "_acceptButton"); + yield return readyButton; + readyButton.Button.OnClick.Invoke(); + + { + var nextButton = new WaitForButton(screens[EEftScreenType.OfflineRaid], "_nextButtonSpawner"); + yield return nextButton; + nextButton.Button.OnClick.Invoke(); + } + + { + var nextButton = new WaitForButton(screens[EEftScreenType.Insurance], "_nextButton"); + yield return nextButton; + nextButton.Button.OnClick.Invoke(); + } + + { + yield return new WaitUntil(() => MatchmakerAcceptScreenShowPatch.MatchmakerObject?.GetComponent() != null); + var guiComp = MatchmakerAcceptScreenShowPatch.MatchmakerObject.GetComponent(); + guiComp.HostSoloRaidAndJoin(ESITProtocol.PeerToPeerUdp, EBotAmount.High); + } + + if (i > 0) + { + yield return new WaitForSeconds(300.0f); + Singleton.Instance.Stop(SITMatchmaking.Profile.ProfileId, ExitStatus.Survived, ""); + + { + yield return new WaitUntil( + () => screens.ContainsKey(EEftScreenType.ExitStatus) && screens[EEftScreenType.ExitStatus].gameObject.activeSelf); + var nextButton = new WaitForButton(screens[EEftScreenType.ExitStatus], "_nextButton"); + yield return nextButton; + nextButton.Button.OnClick.Invoke(); + } + + { + var nextButton = new WaitForButton(screens[EEftScreenType.KillList], "_nextButton"); + yield return nextButton; + nextButton.Button.OnClick.Invoke(); + } + + { + var nextButton = new WaitForButton(screens[EEftScreenType.SessionStatistics], "_nextButton"); + yield return nextButton; + nextButton.Button.OnClick.Invoke(); + } + + { + var nextButton = new WaitForButton(screens[EEftScreenType.SessionExperience], "_nextButton"); + yield return nextButton; + nextButton.Button.OnClick.Invoke(); + } + + { + var profile = app.Session.Profile; + var ic = new InventoryController(app.Session, profile, profile.Id); + var hc = new HealthControllerClass(profile.Health, ic, profile.Skills, regeneration: true); + if (HealthTreatmentScreen.IsAvailable(profile, hc, app.Session.Medic.Info)) + { + var nextButton = new WaitForButton(screens[EEftScreenType.HealthTreatment], "_nextButton"); + yield return nextButton; + nextButton.Button.OnClick.Invoke(); + } + } + } + } + } + finally + { + Destroy(this); + } + } + + StartCoroutine(autoraid()); + } + } + } + + class WaitForButton(UIScreen screen, string fieldName) : CustomYieldInstruction where T : UIScreen where U : class? + { + public U Button; + + public override bool keepWaiting + { + get + { + Button = (U)ReflectionHelpers.GetFieldFromType(typeof(T), fieldName).GetValue(screen); + return Button == null; + } + } + } +} diff --git a/Source/UI/Ammo_CachedReadOnlyAttributes_Patch.cs b/Source/UI/Ammo_CachedReadOnlyAttributes_Patch.cs deleted file mode 100644 index 74d4ada02..000000000 --- a/Source/UI/Ammo_CachedReadOnlyAttributes_Patch.cs +++ /dev/null @@ -1,112 +0,0 @@ -using EFT.InventoryLogic; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace StayInTarkov.UI -{ - - /// - /// Paulov. Adding Damage attribute to UI Template - /// - internal class Ammo_CachedReadOnlyAttributes_Patch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return ReflectionHelpers.GetMethodForType(typeof(AmmoTemplate), "GetCachedReadonlyQualities"); - } - - [PatchPostfix] - private static void Postfix(ref AmmoTemplate __instance, ref List __result) - { - if (!__result.Any((ItemAttributeClass a) => (Attributes.ENewMaximumDurabilityId)a.Id == Attributes.ENewMaximumDurabilityId.Damage)) - { - AddNewAttributes(ref __result, __instance); - } - } - - public static void AddNewAttributes(ref List attributes, AmmoTemplate template) - { - if (template == null) - return; - - // Damage - if (template.Damage > 0) - { - attributes.Add( - new ItemAttributeClass(Attributes.ENewMaximumDurabilityId.Damage) - { - Name = Attributes.ENewMaximumDurabilityId.Damage.GetName(), - Base = (() => template.Damage), - StringValue = (() => template.Damage.ToString()), - DisplayType = (() => EItemAttributeDisplayType.Compact) - } - ); - } - - // Armor Damage - if (template.ArmorDamage > 0) - { - attributes.Add( - new ItemAttributeClass(Attributes.ENewMaximumDurabilityId.ArmorDamage) - { - Name = Attributes.ENewMaximumDurabilityId.ArmorDamage.GetName(), - Base = (() => template.ArmorDamage), - StringValue = (() => template.ArmorDamage.ToString()), - DisplayType = (() => EItemAttributeDisplayType.Compact) - } - ); - } - - // Penetration - if (template.PenetrationPower > 0) - { - attributes.Add( - new ItemAttributeClass(Attributes.ENewMaximumDurabilityId.Penetration) - { - Name = Attributes.ENewMaximumDurabilityId.Penetration.GetName(), - Base = (() => template.PenetrationPower), - StringValue = (() => template.PenetrationPower.ToString()), - DisplayType = (() => EItemAttributeDisplayType.Compact) - } - ); - } - } - } - - public static class Attributes - { - public static string GetName(this Attributes.ENewMaximumDurabilityId id) - { - switch (id) - { - case Attributes.ENewMaximumDurabilityId.Damage: - // return "DAMAGE"; - return StayInTarkovPlugin.LanguageDictionary["DAMAGE"].ToString(); - case Attributes.ENewMaximumDurabilityId.ArmorDamage: - // return "ARMOR DAMAGE"; - return StayInTarkovPlugin.LanguageDictionary["ARMOR_DAMAGE"].ToString(); - case Attributes.ENewMaximumDurabilityId.Penetration: - // return "PENETRATION"; - return StayInTarkovPlugin.LanguageDictionary["PENETRATION"].ToString(); - case Attributes.ENewMaximumDurabilityId.FragmentationChance: - // return "FRAGMENTATION CHANCE"; - return StayInTarkovPlugin.LanguageDictionary["FRAGMENTATION_CHANCE"].ToString(); - case Attributes.ENewMaximumDurabilityId.RicochetChance: - // return "RICOCHET CHANCE"; - return StayInTarkovPlugin.LanguageDictionary["RICOCHET_CHANCE"].ToString(); - default: - return id.ToString(); - } - } - - public enum ENewMaximumDurabilityId - { - Damage, - ArmorDamage, - Penetration, - FragmentationChance, - RicochetChance - } - } -} diff --git a/Source/UI/ConsoleCommands.cs b/Source/UI/ConsoleCommands.cs index 4c8c11d38..e16a5560d 100644 --- a/Source/UI/ConsoleCommands.cs +++ b/Source/UI/ConsoleCommands.cs @@ -1,16 +1,23 @@ using Comfort.Common; using EFT; using EFT.Console.Core; +using EFT.Interactive; using EFT.UI; +using EFT.Weather; using StayInTarkov.Coop.Components.CoopGameComponents; +using StayInTarkov.Coop.FreeCamera; using StayInTarkov.Coop.SITGameModes; using System; using System.IO; +using System.Linq; +using System.Reflection; +using Systems.Effects; +using UnityEngine; using static StayInTarkov.Networking.SITSerialization; namespace StayInTarkov.UI { - public class ConsoleCommands + public class ConsoleCommands : MonoBehaviour { #if DEBUG [ConsoleCommand("mark", "", null, "Save current position as a teleport location", [])] @@ -169,6 +176,137 @@ public static void Players() ConsoleScreen.LogException(ex); } } + [ConsoleCommand("weather", "", null, "Dump weather properties", [])] + public static void weather() + { + try + { + var weatherDebug = EFT.Weather.WeatherController.Instance.WeatherDebug; + PropertyInfo[] properties = typeof(WeatherDebug).GetProperties(); + + foreach (PropertyInfo property in properties) + { + object value = property.GetValue(weatherDebug); + EFT.UI.ConsoleScreen.Log($"{property.Name}: {value}"); + } + } + catch (Exception ex) + { + ConsoleScreen.LogException(ex); + } + } + + [ConsoleCommand("freecam", "", null, "Activates / Deactivates freecam", [])] + public static void Freecam() + { + try + { + FreeCameraController CameraController = Singleton.Instance.gameObject.GetOrAddComponent(); + CameraController.ToggleCamera(); + } + catch(Exception ex) + { + ConsoleScreen.LogException(ex); + } + } + + [ConsoleCommand("freecam.toggleui", "", null, "Activates / Deactivates freecam's ability to hide the UI", [])] + public static void Freecam_ToggleUI() + { + try + { + FreeCameraController CameraController = Singleton.Instance.gameObject.GetOrAddComponent(); + CameraController.ToggleUi(); + } + catch (Exception ex) + { + ConsoleScreen.LogException(ex); + } + } + + [ConsoleCommand("unlockd", "", null, "Unlock doors that can be opened", [])] + public static void UnlockDoors() + { + try + { + foreach (Door door in BSGUnityHelper.FindUnityObjectsOfType()) + { + if ((door.DoorState == EDoorState.Locked && !string.IsNullOrEmpty(door.KeyId)) || door.DoorState == EDoorState.Interacting) + { + door.DoorState = EDoorState.Shut; + } + } + } + catch (Exception ex) + { + ConsoleScreen.LogException(ex); + } + } + + [ConsoleCommand("despawnbots", "", null, "Despawns all AI players", [])] + public static void DespawnBots() + { + try + { + if (Singleton.Instance is CoopSITGame game) + { + foreach (var bot in game.PBotsController.Players.ToList()) + { + if (bot.AIData.BotOwner == null) + { + continue; + } + + //Taken from SWAG + DONUTS TO replicate this behavior + //Credit to dvize & p-kossa for their amazing work + //For reference: https://github.com/dvize/Donuts/blob/727e1de7c2a0714432ab03947deca2bfd5fad699/DonutComponent.cs#L388 + ConsoleScreen.Log($"Despawning bot: {bot.Profile.Info.Nickname}"); + + BotOwner botOwner = bot.AIData.BotOwner; + + Singleton.Instance.EffectsCommutator.StopBleedingForPlayer(botOwner.GetPlayer); + botOwner.Deactivate(); + botOwner.Dispose(); + game.PBotsController.BotDied(botOwner); + game.PBotsController.DestroyInfo(botOwner.GetPlayer); + DestroyImmediate(botOwner.gameObject); + Destroy(botOwner); + } + } + } + catch(Exception ex) + { + ConsoleScreen.LogException(ex); + } + } + + [ConsoleCommand("teleportbots", "", null, "Teleports all AI players to the person running this command", [])] + public static void TeleportBots() + { + try + { + if (Singleton.Instance is CoopSITGame game) + { + var player = Singleton.Instance?.MainPlayer; + UnityEngine.Vector3 playerPositon = player.Position; + + foreach (var bot in game.PBotsController.Players.ToList()) + { + if (bot.AIData.BotOwner == null) + { + continue; + } + + BotOwner botOwner = bot.AIData.BotOwner; + botOwner.GetPlayer.Teleport(playerPositon); + } + } + } + catch (Exception ex) + { + ConsoleScreen.LogException(ex); + } + } private static string tpPath(CoopSITGame game, string name) { diff --git a/Source/UI/OfflineSaveProfile.cs b/Source/UI/OfflineSaveProfile.cs index db237bb9c..745ca312a 100644 --- a/Source/UI/OfflineSaveProfile.cs +++ b/Source/UI/OfflineSaveProfile.cs @@ -1,4 +1,6 @@ -using Comfort.Common; +using BepInEx; +using BepInEx.Bootstrap; +using Comfort.Common; using EFT; using StayInTarkov.AkiSupport.Singleplayer.Models.Healing; using StayInTarkov.Coop.Components.CoopGameComponents; @@ -44,15 +46,35 @@ protected override MethodBase GetTargetMethod() return GetMethod(); } + private static ISession _backEndSession; + public static ISession BackEndSession + { + get + { + if (_backEndSession == null) + { + _backEndSession = Singleton>.Instance.GetClientBackEndSession(); + } + + return _backEndSession; + } + } + [PatchPrefix] public static bool PatchPrefix(string profileId, RaidSettings ____raidSettings, TarkovApplication __instance, Result result) { - Logger.LogInfo("Saving Profile..."); + if (StayInTarkovPlugin.Instance.IsAkiSinglePlayerLoaded()) + { + Logger.LogDebug("SIT: Detected Aki SP Module. Ignoring SIT's Save Profile Method."); + return true; + } + + Logger.LogInfo("SIT: Saving Profile..."); // Get scav or pmc profile based on IsScav value var profile = ____raidSettings.IsScav - ? __instance.GetClientBackEndSession().ProfileOfPet - : __instance.GetClientBackEndSession().Profile; + ? BackEndSession.ProfileOfPet + : BackEndSession.Profile; var currentHealth = HealthListener.Instance.CurrentHealth; @@ -71,13 +93,6 @@ public static bool PatchPrefix(string profileId, RaidSettings ____raidSettings, SaveProfileProgress(result.Value0, profile, currentHealth, ____raidSettings.IsScav); - - var coopGC = SITGameComponent.GetCoopGameComponent(); - if (coopGC != null) - { - UnityEngine.Object.Destroy(coopGC); - } - HealthListener.Instance.MyHealthController = null; return true; } @@ -88,18 +103,6 @@ public static void SaveProfileProgress(ExitStatus exitStatus, Profile profileDat if (exitStatus == ExitStatus.Left) exitStatus = ExitStatus.Runner; - // TODO: Remove uneccessary data - //var clonedProfile = profileData.Clone(); - //clonedProfile.Encyclopedia = null; - //clonedProfile.Hideout = null; - //clonedProfile.Notes = null; - //clonedProfile.RagfairInfo = null; - //clonedProfile.Skills = null; - //clonedProfile.TradersInfo = null; - //clonedProfile.QuestsData = null; - //clonedProfile.UnlockedRecipeInfo = null; - //clonedProfile.WishList = null; - SaveProfileRequest request = new() { exit = exitStatus.ToString().ToLower(), @@ -109,13 +112,7 @@ public static void SaveProfileProgress(ExitStatus exitStatus, Profile profileDat }; var convertedJson = request.SITToJson(); - //Logger.LogDebug("SaveProfileProgress ====================================================="); - //Logger.LogDebug(convertedJson); - AkiBackendCommunication.Instance.PostJson("/raid/profile/save", convertedJson); - //_ = AkiBackendCommunication.Instance.PostJsonAsync("/raid/profile/save", convertedJson, timeout: 10 * 1000, debug: false); - - - //Request.Instance.PostJson("/raid/profile/save", convertedJson, timeout: 60 * 1000, debug: true); + AkiBackendCommunication.Instance.PostJsonBLOCKING("/raid/profile/save", convertedJson); } public class SaveProfileRequest @@ -123,7 +120,7 @@ public class SaveProfileRequest public string exit { get; set; } public Profile profile { get; set; } public bool isPlayerScav { get; set; } - public object health { get; set; } + public PlayerHealth health { get; set; } } } } diff --git a/Source/UI/OfflineSettingsScreenPatch.cs b/Source/UI/OfflineSettingsScreenPatch.cs index 2650ad94d..05dbb0842 100644 --- a/Source/UI/OfflineSettingsScreenPatch.cs +++ b/Source/UI/OfflineSettingsScreenPatch.cs @@ -23,7 +23,7 @@ protected override MethodBase GetTargetMethod() [PatchPrefix] public static bool Prefix( MatchmakerOfflineRaidScreen __instance - , ProfileInfo profileInfo + , InfoClass profileInfo , RaidSettings raidSettings , UpdatableToggle ____offlineModeToggle , DefaultUIButton ____changeSettingsButton @@ -53,7 +53,7 @@ MatchmakerOfflineRaidScreen __instance [PatchPostfix] public static void PatchPostfix( MatchmakerOfflineRaidScreen __instance - , ProfileInfo profileInfo + , InfoClass profileInfo , RaidSettings raidSettings , UpdatableToggle ____offlineModeToggle , DefaultUIButton ____changeSettingsButton @@ -86,7 +86,7 @@ MatchmakerOfflineRaidScreen __instance public static void RemoveBlockers( MatchmakerOfflineRaidScreen __instance - , ProfileInfo profileInfo + , InfoClass profileInfo , RaidSettings raidSettings , UpdatableToggle ____offlineModeToggle , DefaultUIButton ____changeSettingsButton diff --git a/StayInTarkov.sln b/StayInTarkov.sln index 4be7ce803..2c9ea450f 100644 --- a/StayInTarkov.sln +++ b/StayInTarkov.sln @@ -22,14 +22,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{A9DA EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{DEB9463E-0556-44FD-A150-BFAFBB433D0B}" ProjectSection(SolutionItems) = preProject - .github\workflows\ci.yml = .github\workflows\ci.yml + .github\workflows\comment-on-pr.yml = .github\workflows\comment-on-pr.yml .github\workflows\publish-wiki.yml = .github\workflows\publish-wiki.yml .github\workflows\SIT-CD.yml = .github\workflows\SIT-CD.yml .github\workflows\SIT-CI-Linux.yml = .github\workflows\SIT-CI-Linux.yml - .github\workflows\SIT-CI.yml = .github\workflows\SIT-CI.yml - .github\workflows\stay-in-tarkov-client.yml = .github\workflows\stay-in-tarkov-client.yml EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SIT.WildSpawnType.PrePatcher", "SIT.WildSpawnType.PrePatcher\SIT.WildSpawnType.PrePatcher.csproj", "{D5AF3F81-9EAC-4584-A30C-73E6DF3B73E6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -40,6 +40,10 @@ Global {79F0E889-A195-42B4-8656-4F35685BBB80}.Debug|Any CPU.Build.0 = Debug|Any CPU {79F0E889-A195-42B4-8656-4F35685BBB80}.Release|Any CPU.ActiveCfg = Release|Any CPU {79F0E889-A195-42B4-8656-4F35685BBB80}.Release|Any CPU.Build.0 = Release|Any CPU + {D5AF3F81-9EAC-4584-A30C-73E6DF3B73E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5AF3F81-9EAC-4584-A30C-73E6DF3B73E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5AF3F81-9EAC-4584-A30C-73E6DF3B73E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5AF3F81-9EAC-4584-A30C-73E6DF3B73E6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 000000000..1094f81e8 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,9 @@ +{ + "dependencies": [ + { + "name": "flatbuffers", + "version>=": "23.5.26" + } + ], + "builtin-baseline": "11ed79186fe850bd3a98cfbf1854514d2b3070a2" +}