mirror of
https://github.com/sp-tarkov/modules.git
synced 2025-02-13 09:50:43 -05:00
Merge tag '3.8.0'
# Conflicts: # project/Aki.Core/AkiCorePlugin.cs # project/Aki.Core/Utils/ValidationUtil.cs # project/Aki.Custom/Aki.Custom.csproj # project/Aki.Custom/Patches/CustomAiPatch.cs # project/Aki.Debugging/AkiDebuggingPlugin.cs # project/Aki.SinglePlayer/AkiSingleplayerPlugin.cs # project/Aki.SinglePlayer/Patches/Progression/OfflineSaveProfilePatch.cs # project/Aki.SinglePlayer/Patches/RaidFix/LabsKeycardRemovalPatch.cs # project/Aki.SinglePlayer/Patches/ScavMode/ScavLateStartPatch.cs
This commit is contained in:
commit
688ff6f334
35
.gitea/workflows/build-trigger.yaml
Normal file
35
.gitea/workflows/build-trigger.yaml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: Trigger Main Build Pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trigger-main-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Setup Git Config
|
||||||
|
run: |
|
||||||
|
git config --global user.email "noreply@sp-tarkov.com"
|
||||||
|
git config --global user.name "TriggerBot"
|
||||||
|
|
||||||
|
- name: Clone Build Repository
|
||||||
|
run: |
|
||||||
|
rm -rf ../Build
|
||||||
|
git clone https://${{ secrets.BUILD_USERNAME }}:${{ secrets.BUILD_ACCESS_TOKEN }}@dev.sp-tarkov.com/SPT-AKI/Build.git ../Build
|
||||||
|
|
||||||
|
- name: Trigger Branch
|
||||||
|
working-directory: ../Build
|
||||||
|
run: git checkout -b trigger || git checkout trigger
|
||||||
|
|
||||||
|
- name: Create Trigger File
|
||||||
|
working-directory: ../Build
|
||||||
|
run: |
|
||||||
|
echo "${GITHUB_REF_NAME}" > .gitea/trigger
|
||||||
|
git add .gitea/trigger
|
||||||
|
git commit -m "Modules triggered build with tag '${GITHUB_REF_NAME}'"
|
||||||
|
|
||||||
|
- name: Force Push
|
||||||
|
working-directory: ../Build
|
||||||
|
run: git push --force origin trigger
|
33
README.md
33
README.md
@ -22,26 +22,31 @@ git config --local user.email "USERNAME@SOMETHING.com"
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
- Escape From Tarkov 29197
|
||||||
- Escape From Tarkov 26535
|
- Visual Studio Code -OR- Visual Studio 2022
|
||||||
- BepInEx 5.4.21
|
|
||||||
- Visual Studio Code
|
|
||||||
- .NET 6 SDK
|
- .NET 6 SDK
|
||||||
|
- [PowerShell v7](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows)
|
||||||
|
- Can also be installed via: `dotnet tool update --global PowerShell`
|
||||||
|
|
||||||
## Setup
|
## Project Setup
|
||||||
|
Copy-paste Live EFT's `EscapeFromTarkov_Data/Managed/` folder to into this project's `Project/Shared/Managed/` folder
|
||||||
|
|
||||||
Copy-paste Live EFT's `EscapeFromTarkov_Data/Managed/` folder to into Modules' `Project/Shared/` folder
|
## Build (VS Code)
|
||||||
|
|
||||||
## Build (vscode)
|
|
||||||
1. File > Open Workspace > Modules.code-workspace
|
1. File > Open Workspace > Modules.code-workspace
|
||||||
2. Terminal > Run Build Task...
|
2. Terminal > Run Build Task...
|
||||||
3. Copy contents of `/Build` into SPT game folder and overwrite
|
3. Copy contents of `/Build` into SPT game folder and overwrite
|
||||||
|
|
||||||
## Build (VS)
|
## Build (VS 2022)
|
||||||
1. Open solution
|
1. Open solution
|
||||||
2. Restore nuget packages
|
2. Restore nuget packages
|
||||||
3. Run `dotnet new tool-manifest`
|
3. Build solution
|
||||||
4. Sometimes you need to run `dotnet tool restore`
|
4. Copy contents of `/Build` into SPT game folder and overwrite
|
||||||
5. Run `dotnet tool install Cake.Tool`
|
|
||||||
6. Build solution
|
## Game Setup
|
||||||
7. Copy contents of `/Build` into SPT game folder and overwrite
|
1. Copy Live EFT files into a separate directory (from now on this will be referred to as the "SPT directory")
|
||||||
|
2. Download BepInEx 5.4.22 x64 ([BepInEx Releases - GitHub](https://github.com/BepInEx/BepInEx/releases/tag/v5.4.22))
|
||||||
|
3. Extract contents of the BepInEx zip into the root SPT directory
|
||||||
|
4. Build Modules, Server and Launcher
|
||||||
|
5. Copy the contents of each project's `Build` folder into the root SPT directory
|
||||||
|
6. (Optional, but recommended) Download the BepInEx5 version of ConfigurationManager ([ConfigurationManager Releases - GitHub](https://github.com/BepInEx/BepInEx.ConfigurationManager/releases)) and extract the contents of the zip into the root SPT directory. The default keybind for opening the menu will be `F1`
|
||||||
|
7. (Optional) Edit the BepInEx config (`\BepInEx\config\BepInEx.cfg`) and append `Debug` to the `LogLevels` setting. Example: `LogLevels = Fatal, Error, Warning, Message, Info, Debug`
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"isRoot": true,
|
|
||||||
"tools": {
|
|
||||||
"cake.tool": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"commands": [
|
|
||||||
"dotnet-cake"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net471</TargetFramework>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Company>SPT Aki</Company>
|
<Company>SPT Aki</Company>
|
||||||
<Copyright>Copyright @ SPT Aki 2023</Copyright>
|
<Copyright>Copyright @ SPT Aki 2024</Copyright>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -19,11 +20,14 @@
|
|||||||
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||||
<ProjectReference Include="..\Aki.Core\Aki.Core.csproj" />
|
<ProjectReference Include="..\Aki.Core\Aki.Core.csproj" />
|
||||||
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.PrePatch\Aki.PrePatch.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Debugging\Aki.Debugging.csproj" />
|
||||||
<ProjectReference Include="..\Aki.SinglePlayer\Aki.SinglePlayer.csproj" />
|
<ProjectReference Include="..\Aki.SinglePlayer\Aki.SinglePlayer.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Custom\Aki.Custom.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
<Exec Command="dotnet cake "../build.cake" --vsbuilt=true" />
|
<Exec Command="pwsh -NoProfile -ExecutionPolicy Bypass ../build.ps1" WorkingDirectory="$(ProjectDir)" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -2,15 +2,17 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>1.0.0.0</Version>
|
<Version>1.0.0.0</Version>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net471</TargetFramework>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Company>Aki</Company>
|
<Company>Aki</Company>
|
||||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
<Copyright>Copyright @ Aki 2024</Copyright>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="System.Net.http" />
|
||||||
<Reference Include="bsg.componentace.compression.libs.zlib" HintPath="..\Shared\Managed\bsg.componentace.compression.libs.zlib.dll" Private="False" />
|
<Reference Include="bsg.componentace.compression.libs.zlib" HintPath="..\Shared\Managed\bsg.componentace.compression.libs.zlib.dll" Private="False" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
161
project/Aki.Common/Http/Client.cs
Normal file
161
project/Aki.Common/Http/Client.cs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Common.Utils;
|
||||||
|
|
||||||
|
namespace Aki.Common.Http
|
||||||
|
{
|
||||||
|
// NOTE: you do not want to dispose this, keep a reference for the lifetime
|
||||||
|
// of the application.
|
||||||
|
// NOTE: cannot be made async due to Unity's limitations.
|
||||||
|
public class Client : IDisposable
|
||||||
|
{
|
||||||
|
protected readonly HttpClient _httpv;
|
||||||
|
protected readonly string _address;
|
||||||
|
protected readonly string _accountId;
|
||||||
|
protected readonly int _retries;
|
||||||
|
|
||||||
|
public Client(string address, string accountId, int retries = 3)
|
||||||
|
{
|
||||||
|
_address = address;
|
||||||
|
_accountId = accountId;
|
||||||
|
_retries = retries;
|
||||||
|
|
||||||
|
var handler = new HttpClientHandler()
|
||||||
|
{
|
||||||
|
// force setting cookies in header instead of CookieContainer
|
||||||
|
UseCookies = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_httpv = new HttpClient(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequestMessage GetNewRequest(HttpMethod method, string path)
|
||||||
|
{
|
||||||
|
return new HttpRequestMessage()
|
||||||
|
{
|
||||||
|
Method = method,
|
||||||
|
RequestUri = new Uri(_address + path),
|
||||||
|
Headers = {
|
||||||
|
{ "Cookie", $"PHPSESSID={_accountId}" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] Send(HttpMethod method, string path, byte[] data, bool compress = true)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = null;
|
||||||
|
|
||||||
|
using (var request = GetNewRequest(method, path))
|
||||||
|
{
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
// if there is data, convert to payload
|
||||||
|
byte[] payload = (compress)
|
||||||
|
? Zlib.Compress(data, ZlibCompression.Maximum)
|
||||||
|
: data;
|
||||||
|
|
||||||
|
// add payload to request
|
||||||
|
request.Content = new ByteArrayContent(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send request
|
||||||
|
response = _httpv.SendAsync(request).Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
// response error
|
||||||
|
throw new Exception($"Code {response.StatusCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var stream = response.Content.ReadAsStreamAsync().Result)
|
||||||
|
{
|
||||||
|
// grap response payload
|
||||||
|
stream.CopyTo(ms);
|
||||||
|
var bytes = ms.ToArray();
|
||||||
|
|
||||||
|
if (bytes != null)
|
||||||
|
{
|
||||||
|
// payload contains data
|
||||||
|
return Zlib.IsCompressed(bytes)
|
||||||
|
? Zlib.Decompress(bytes)
|
||||||
|
: bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// response returned no data
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Get(string path)
|
||||||
|
{
|
||||||
|
var error = new Exception("Internal error");
|
||||||
|
|
||||||
|
// NOTE: <= is intentional, 0 is send, 1,2,3 is retry
|
||||||
|
for (var i = 0; i <= _retries; ++i)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Send(HttpMethod.Get, path, null, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Post(string path, byte[] data, bool compressed = true)
|
||||||
|
{
|
||||||
|
var error = new Exception("Internal error");
|
||||||
|
|
||||||
|
// NOTE: <= is intentional, 0 is send, 1,2,3 is retry
|
||||||
|
for (var i = 0; i <= _retries; ++i)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Send(HttpMethod.Post, path, data, compressed);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Put(string path, byte[] data, bool compressed = true)
|
||||||
|
{
|
||||||
|
var error = new Exception("Internal error");
|
||||||
|
|
||||||
|
// NOTE: <= is intentional, 0 is send, 1,2,3 is retry
|
||||||
|
for (var i = 0; i <= _retries; ++i)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Send(HttpMethod.Put, path, data, compressed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_httpv.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
#region DEPRECATED, REMOVE IN 3.8.1
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -6,12 +8,10 @@ using Aki.Common.Utils;
|
|||||||
|
|
||||||
namespace Aki.Common.Http
|
namespace Aki.Common.Http
|
||||||
{
|
{
|
||||||
|
[Obsolete("Request is deprecated, please use Aki.Common.Http.Client instead.")]
|
||||||
public class Request
|
public class Request
|
||||||
{
|
{
|
||||||
/// <summary>
|
[Obsolete("Request.Send() is deprecated, please use Aki.Common.Http.Client instead.")]
|
||||||
/// Send a request to remote endpoint and optionally receive a response body.
|
|
||||||
/// Deflate is the accepted compression format.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] Send(string url, string method, byte[] data = null, bool compress = true, string mime = null, Dictionary<string, string> headers = null)
|
public byte[] Send(string url, string method, byte[] data = null, bool compress = true, string mime = null, Dictionary<string, string> headers = null)
|
||||||
{
|
{
|
||||||
if (!WebConstants.IsValidMethod(method))
|
if (!WebConstants.IsValidMethod(method))
|
||||||
@ -81,3 +81,5 @@ namespace Aki.Common.Http
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
@ -1,49 +1,46 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Aki.Common.Utils;
|
using System.Threading.Tasks;
|
||||||
using BepInEx.Logging;
|
using BepInEx.Logging;
|
||||||
|
using Aki.Common.Utils;
|
||||||
|
|
||||||
namespace Aki.Common.Http
|
namespace Aki.Common.Http
|
||||||
{
|
{
|
||||||
public static class RequestHandler
|
public static class RequestHandler
|
||||||
{
|
{
|
||||||
private static string _host;
|
|
||||||
private static string _session;
|
|
||||||
private static Request _request;
|
|
||||||
private static Dictionary<string, string> _headers;
|
|
||||||
private static ManualLogSource _logger;
|
private static ManualLogSource _logger;
|
||||||
|
public static readonly Client HttpClient;
|
||||||
|
public static readonly string Host;
|
||||||
|
public static readonly string SessionId;
|
||||||
|
public static readonly bool IsLocal;
|
||||||
|
|
||||||
static RequestHandler()
|
static RequestHandler()
|
||||||
{
|
{
|
||||||
_logger = Logger.CreateLogSource(nameof(RequestHandler));
|
_logger = Logger.CreateLogSource(nameof(RequestHandler));
|
||||||
Initialize();
|
|
||||||
}
|
// grab required info from command args
|
||||||
|
var args = Environment.GetCommandLineArgs();
|
||||||
|
|
||||||
private static void Initialize()
|
foreach (var arg in args)
|
||||||
{
|
|
||||||
_request = new Request();
|
|
||||||
|
|
||||||
string[] args = Environment.GetCommandLineArgs();
|
|
||||||
|
|
||||||
foreach (string arg in args)
|
|
||||||
{
|
{
|
||||||
if (arg.Contains("BackendUrl"))
|
if (arg.Contains("BackendUrl"))
|
||||||
{
|
{
|
||||||
string json = arg.Replace("-config=", string.Empty);
|
var json = arg.Replace("-config=", string.Empty);
|
||||||
_host = Json.Deserialize<ServerConfig>(json).BackendUrl;
|
Host = Json.Deserialize<ServerConfig>(json).BackendUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg.Contains("-token="))
|
if (arg.Contains("-token="))
|
||||||
{
|
{
|
||||||
_session = arg.Replace("-token=", string.Empty);
|
SessionId = arg.Replace("-token=", string.Empty);
|
||||||
_headers = new Dictionary<string, string>()
|
|
||||||
{
|
|
||||||
{ "Cookie", $"PHPSESSID={_session}" },
|
|
||||||
{ "SessionId", _session }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IsLocal = Host.Contains("127.0.0.1")
|
||||||
|
|| Host.Contains("localhost");
|
||||||
|
|
||||||
|
// initialize http client
|
||||||
|
HttpClient = new Client(Host, SessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateData(byte[] data)
|
private static void ValidateData(byte[] data)
|
||||||
@ -66,46 +63,129 @@ namespace Aki.Common.Http
|
|||||||
_logger.LogInfo($"Request was successful");
|
_logger.LogInfo($"Request was successful");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] GetData(string path, bool hasHost = false)
|
public static byte[] GetData(string path)
|
||||||
{
|
{
|
||||||
string url = (hasHost) ? path : _host + path;
|
_logger.LogInfo($"Request GET data: {SessionId}:{path}");
|
||||||
|
|
||||||
|
var data = HttpClient.Get(path);
|
||||||
|
|
||||||
_logger.LogInfo($"Request GET data: {_session}:{url}");
|
ValidateData(data);
|
||||||
byte[] result = _request.Send(url, "GET", null, headers: _headers);
|
return data;
|
||||||
|
|
||||||
ValidateData(result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetJson(string path, bool hasHost = false)
|
public static string GetJson(string path)
|
||||||
{
|
{
|
||||||
string url = (hasHost) ? path : _host + path;
|
_logger.LogInfo($"Request GET json: {SessionId}:{path}");
|
||||||
|
|
||||||
|
var payload = HttpClient.Get(path);
|
||||||
|
var body = Encoding.UTF8.GetString(payload);
|
||||||
|
|
||||||
_logger.LogInfo($"Request GET json: {_session}:{url}");
|
ValidateJson(body);
|
||||||
byte[] data = _request.Send(url, "GET", headers: _headers);
|
return body;
|
||||||
string result = Encoding.UTF8.GetString(data);
|
|
||||||
|
|
||||||
ValidateJson(result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string PostJson(string path, string json, bool hasHost = false)
|
public static string PostJson(string path, string json)
|
||||||
{
|
{
|
||||||
string url = (hasHost) ? path : _host + path;
|
_logger.LogInfo($"Request POST json: {SessionId}:{path}");
|
||||||
|
|
||||||
|
var payload = Encoding.UTF8.GetBytes(json);
|
||||||
|
var data = HttpClient.Post(path, payload);
|
||||||
|
var body = Encoding.UTF8.GetString(data);
|
||||||
|
|
||||||
_logger.LogInfo($"Request POST json: {_session}:{url}");
|
ValidateJson(body);
|
||||||
byte[] data = _request.Send(url, "POST", Encoding.UTF8.GetBytes(json), true, "application/json", _headers);
|
return body;
|
||||||
string result = Encoding.UTF8.GetString(data);
|
|
||||||
|
|
||||||
ValidateJson(result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void PutJson(string path, string json, bool hasHost = false)
|
public static void PutJson(string path, string json)
|
||||||
{
|
{
|
||||||
string url = (hasHost) ? path : _host + path;
|
_logger.LogInfo($"Request PUT json: {SessionId}:{path}");
|
||||||
_logger.LogInfo($"Request PUT json: {_session}:{url}");
|
|
||||||
_request.Send(url, "PUT", Encoding.UTF8.GetBytes(json), true, "application/json", _headers);
|
var payload = Encoding.UTF8.GetBytes(json);
|
||||||
|
HttpClient.Put(path, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region DEPRECATED, REMOVE IN 3.8.1
|
||||||
|
[Obsolete("GetData(path, isHost) is deprecated, please use GetData(path) instead.")]
|
||||||
|
public static byte[] GetData(string path, bool hasHost)
|
||||||
|
{
|
||||||
|
var url = (hasHost) ? path : Host + path;
|
||||||
|
_logger.LogInfo($"Request GET data: {SessionId}:{url}");
|
||||||
|
|
||||||
|
var headers = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "Cookie", $"PHPSESSID={SessionId}" },
|
||||||
|
{ "SessionId", SessionId }
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = new Request();
|
||||||
|
var data = request.Send(url, "GET", null, headers: headers);
|
||||||
|
|
||||||
|
ValidateData(data);
|
||||||
|
return data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("GetJson(path, isHost) is deprecated, please use GetJson(path) instead.")]
|
||||||
|
public static string GetJson(string path, bool hasHost)
|
||||||
|
{
|
||||||
|
var url = (hasHost) ? path : Host + path;
|
||||||
|
_logger.LogInfo($"Request GET json: {SessionId}:{url}");
|
||||||
|
|
||||||
|
var headers = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "Cookie", $"PHPSESSID={SessionId}" },
|
||||||
|
{ "SessionId", SessionId }
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = new Request();
|
||||||
|
var data = request.Send(url, "GET", headers: headers);
|
||||||
|
var body = Encoding.UTF8.GetString(data);
|
||||||
|
|
||||||
|
ValidateJson(body);
|
||||||
|
return body;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("PostJson(path, json, isHost) is deprecated, please use PostJson(path, json) instead.")]
|
||||||
|
public static string PostJson(string path, string json, bool hasHost)
|
||||||
|
{
|
||||||
|
var url = (hasHost) ? path : Host + path;
|
||||||
|
_logger.LogInfo($"Request POST json: {SessionId}:{url}");
|
||||||
|
|
||||||
|
var payload = Encoding.UTF8.GetBytes(json);
|
||||||
|
var mime = WebConstants.Mime[".json"];
|
||||||
|
var headers = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "Cookie", $"PHPSESSID={SessionId}" },
|
||||||
|
{ "SessionId", SessionId }
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = new Request();
|
||||||
|
var data = request.Send(url, "POST", payload, true, mime, headers);
|
||||||
|
var body = Encoding.UTF8.GetString(data);
|
||||||
|
|
||||||
|
ValidateJson(body);
|
||||||
|
return body;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("PutJson(path, json, isHost) is deprecated, please use PutJson(path, json) instead.")]
|
||||||
|
public static void PutJson(string path, string json, bool hasHost)
|
||||||
|
{
|
||||||
|
var url = (hasHost) ? path : Host + path;
|
||||||
|
_logger.LogInfo($"Request PUT json: {SessionId}:{url}");
|
||||||
|
|
||||||
|
var payload = Encoding.UTF8.GetBytes(json);
|
||||||
|
var mime = WebConstants.Mime[".json"];
|
||||||
|
var headers = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "Cookie", $"PHPSESSID={SessionId}" },
|
||||||
|
{ "SessionId", SessionId }
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = new Request();
|
||||||
|
request.Send(url, "PUT", payload, true, mime, headers);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,39 @@
|
|||||||
using System.Collections.Generic;
|
#region DEPRECATED, REMOVE IN 3.8.1
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Aki.Common.Http
|
namespace Aki.Common.Http
|
||||||
{
|
{
|
||||||
|
[Obsolete("WebConstants is deprecated, please use System.Net.Http functionality instead.")]
|
||||||
public static class WebConstants
|
public static class WebConstants
|
||||||
{
|
{
|
||||||
/// <summary>
|
[Obsolete("Get is deprecated, please use HttpMethod.Get instead.")]
|
||||||
/// HTML GET method.
|
|
||||||
/// </summary>
|
|
||||||
public const string Get = "GET";
|
public const string Get = "GET";
|
||||||
|
|
||||||
/// <summary>
|
[Obsolete("Head is deprecated, please use HttpMethod.Head instead.")]
|
||||||
/// HTML HEAD method.
|
|
||||||
/// </summary>
|
|
||||||
public const string Head = "HEAD";
|
public const string Head = "HEAD";
|
||||||
|
|
||||||
/// <summary>
|
[Obsolete("Post is deprecated, please use HttpMethod.Post instead.")]
|
||||||
/// HTML POST method.
|
|
||||||
/// </summary>
|
|
||||||
public const string Post = "POST";
|
public const string Post = "POST";
|
||||||
|
|
||||||
/// <summary>
|
[Obsolete("Put is deprecated, please use HttpMethod.Put instead.")]
|
||||||
/// HTML PUT method.
|
|
||||||
/// </summary>
|
|
||||||
public const string Put = "PUT";
|
public const string Put = "PUT";
|
||||||
|
|
||||||
/// <summary>
|
[Obsolete("Delete is deprecated, please use HttpMethod.Delete instead.")]
|
||||||
/// HTML DELETE method.
|
|
||||||
/// </summary>
|
|
||||||
public const string Delete = "DELETE";
|
public const string Delete = "DELETE";
|
||||||
|
|
||||||
/// <summary>
|
[Obsolete("Connect is deprecated, please use HttpMethod.Connect instead.")]
|
||||||
/// HTML CONNECT method.
|
|
||||||
/// </summary>
|
|
||||||
public const string Connect = "CONNECT";
|
public const string Connect = "CONNECT";
|
||||||
|
|
||||||
/// <summary>
|
[Obsolete("Options is deprecated, please use HttpMethod.Options instead.")]
|
||||||
/// HTML OPTIONS method.
|
|
||||||
/// </summary>
|
|
||||||
public const string Options = "OPTIONS";
|
public const string Options = "OPTIONS";
|
||||||
|
|
||||||
/// <summary>
|
[Obsolete("Trace is deprecated, please use HttpMethod.Trace instead.")]
|
||||||
/// HTML TRACE method.
|
|
||||||
/// </summary>
|
|
||||||
public const string Trace = "TRACE";
|
public const string Trace = "TRACE";
|
||||||
|
|
||||||
/// <summary>
|
[Obsolete("Mime is deprecated, there is sadly no replacement.")]
|
||||||
/// HTML MIME types.
|
|
||||||
/// </summary>
|
|
||||||
public static Dictionary<string, string> Mime { get; private set; }
|
public static Dictionary<string, string> Mime { get; private set; }
|
||||||
|
|
||||||
static WebConstants()
|
static WebConstants()
|
||||||
@ -68,9 +54,7 @@ namespace Aki.Common.Http
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
[Obsolete("IsValidMethod is deprecated, please check against HttpMethod entries instead.")]
|
||||||
/// Is HTML method valid?
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsValidMethod(string method)
|
public static bool IsValidMethod(string method)
|
||||||
{
|
{
|
||||||
return method == Get
|
return method == Get
|
||||||
@ -83,12 +67,12 @@ namespace Aki.Common.Http
|
|||||||
|| method == Trace;
|
|| method == Trace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
[Obsolete("isValidMime is deprecated, there is sadly no replacement available.")]
|
||||||
/// Is MIME type valid?
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsValidMime(string mime)
|
public static bool IsValidMime(string mime)
|
||||||
{
|
{
|
||||||
return Mime.Any(x => x.Value == mime);
|
return Mime.Any(x => x.Value == mime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
@ -1,127 +1,86 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using ComponentAce.Compression.Libs.zlib;
|
using ComponentAce.Compression.Libs.zlib;
|
||||||
|
|
||||||
namespace Aki.Common.Utils
|
namespace Aki.Common.Utils
|
||||||
{
|
{
|
||||||
public enum ZlibCompression
|
public enum ZlibCompression
|
||||||
{
|
{
|
||||||
Store = 0,
|
Store = 0,
|
||||||
Fastest = 1,
|
Fastest = 1,
|
||||||
Fast = 3,
|
Fast = 3,
|
||||||
Normal = 5,
|
Normal = 5,
|
||||||
Ultra = 7,
|
Ultra = 7,
|
||||||
Maximum = 9
|
Maximum = 9
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Zlib
|
public static class Zlib
|
||||||
{
|
{
|
||||||
// Level | CM/CI FLG
|
/// <summary>
|
||||||
// ----- | ---------
|
/// Check if the file is ZLib compressed
|
||||||
// 1 | 78 01
|
/// </summary>
|
||||||
// 2 | 78 5E
|
/// <param name="data">Data</param>
|
||||||
// 3 | 78 5E
|
/// <returns>If the file is Zlib compressed</returns>
|
||||||
// 4 | 78 5E
|
public static bool IsCompressed(byte[] data)
|
||||||
// 5 | 78 5E
|
{
|
||||||
// 6 | 78 9C
|
if (data == null || data.Length < 3)
|
||||||
// 7 | 78 DA
|
{
|
||||||
// 8 | 78 DA
|
return false;
|
||||||
// 9 | 78 DA
|
}
|
||||||
|
|
||||||
/// <summary>
|
// data[0]: Info (CM/CINFO) Header; must be 0x78
|
||||||
/// Check if the file is ZLib compressed
|
if (data[0] != 0x78)
|
||||||
/// </summary>
|
{
|
||||||
/// <param name="Data">Data</param>
|
return false;
|
||||||
/// <returns>If the file is Zlib compressed</returns>
|
}
|
||||||
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 || Data[0] != 0x78)
|
// data[1]: Flags (FLG) Header; compression level.
|
||||||
{
|
switch (data[1])
|
||||||
return false;
|
{
|
||||||
}
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
switch (Data[1])
|
return false;
|
||||||
{
|
}
|
||||||
case 0x01: // fastest
|
|
||||||
case 0x5E: // low
|
|
||||||
case 0x9C: // normal
|
|
||||||
case 0xDA: // max
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
private static byte[] Run(byte[] data, ZlibCompression level)
|
||||||
}
|
{
|
||||||
|
// ZOutputStream.Close() flushes itself.
|
||||||
|
// ZOutputStream.Flush() flushes the target stream.
|
||||||
|
// It's fucking stupid, but whatever.
|
||||||
|
// -- Waffle.Lord, 2022-12-01
|
||||||
|
|
||||||
/// <summary>
|
using (var ms = new MemoryStream())
|
||||||
/// Deflate data.
|
{
|
||||||
/// </summary>
|
using (var zs = (level > ZlibCompression.Store)
|
||||||
public static byte[] Compress(byte[] data, ZlibCompression level)
|
? new ZOutputStream(ms, (int)level)
|
||||||
{
|
: new ZOutputStream(ms))
|
||||||
byte[] buffer = new byte[data.Length + 24];
|
{
|
||||||
|
zs.Write(data, 0, data.Length);
|
||||||
|
}
|
||||||
|
// <-- zs flushes everything here
|
||||||
|
|
||||||
ZStream zs = new ZStream()
|
return ms.ToArray();
|
||||||
{
|
}
|
||||||
avail_in = data.Length,
|
}
|
||||||
next_in = data,
|
|
||||||
next_in_index = 0,
|
|
||||||
avail_out = buffer.Length,
|
|
||||||
next_out = buffer,
|
|
||||||
next_out_index = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
zs.deflateInit((int)level);
|
/// <summary>
|
||||||
zs.deflate(zlibConst.Z_FINISH);
|
/// Deflate data.
|
||||||
|
/// </summary>
|
||||||
data = new byte[zs.next_out_index];
|
public static byte[] Compress(byte[] data, ZlibCompression level)
|
||||||
Array.Copy(zs.next_out, 0, data, 0, zs.next_out_index);
|
{
|
||||||
|
return Run(data, level);
|
||||||
return data;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inflate data.
|
/// Inflate data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static byte[] Decompress(byte[] data)
|
public static byte[] Decompress(byte[] data)
|
||||||
{
|
{
|
||||||
byte[] buffer = new byte[4096];
|
return Run(data, ZlibCompression.Store);
|
||||||
|
}
|
||||||
ZStream zs = new ZStream()
|
}
|
||||||
{
|
|
||||||
avail_in = data.Length,
|
|
||||||
next_in = data,
|
|
||||||
next_in_index = 0,
|
|
||||||
avail_out = buffer.Length,
|
|
||||||
next_out = buffer,
|
|
||||||
next_out_index = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
zs.inflateInit();
|
|
||||||
|
|
||||||
using (MemoryStream ms = new MemoryStream())
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
zs.avail_out = buffer.Length;
|
|
||||||
zs.next_out = buffer;
|
|
||||||
zs.next_out_index = 0;
|
|
||||||
|
|
||||||
int result = zs.inflate(0);
|
|
||||||
|
|
||||||
if (result != 0 && result != 1)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ms.Write(zs.next_out, 0, zs.next_out_index);
|
|
||||||
}
|
|
||||||
while (zs.avail_in > 0 || zs.avail_out == 0);
|
|
||||||
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,13 +1,14 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net471</TargetFramework>
|
||||||
<AssemblyName>aki-core</AssemblyName>
|
<AssemblyName>aki-core</AssemblyName>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Company>Aki</Company>
|
<Company>Aki</Company>
|
||||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
<Copyright>Copyright @ Aki 2024</Copyright>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -8,14 +8,20 @@ namespace Aki.Core
|
|||||||
[BepInPlugin("com.spt-aki.core", "AKI.Core", AkiPluginInfo.PLUGIN_VERSION)]
|
[BepInPlugin("com.spt-aki.core", "AKI.Core", AkiPluginInfo.PLUGIN_VERSION)]
|
||||||
class AkiCorePlugin : BaseUnityPlugin
|
class AkiCorePlugin : BaseUnityPlugin
|
||||||
{
|
{
|
||||||
|
// Temp static logger field, remove along with plugin whitelisting before release
|
||||||
|
internal static BepInEx.Logging.ManualLogSource _logger;
|
||||||
|
|
||||||
public void Awake()
|
public void Awake()
|
||||||
{
|
{
|
||||||
|
_logger = Logger;
|
||||||
|
|
||||||
Logger.LogInfo("Loading: Aki.Core");
|
Logger.LogInfo("Loading: Aki.Core");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
new ConsistencySinglePatch().Enable();
|
new ConsistencySinglePatch().Enable();
|
||||||
new ConsistencyMultiPatch().Enable();
|
new ConsistencyMultiPatch().Enable();
|
||||||
|
new GameValidationPatch().Enable();
|
||||||
new BattlEyePatch().Enable();
|
new BattlEyePatch().Enable();
|
||||||
new SslCertificatePatch().Enable();
|
new SslCertificatePatch().Enable();
|
||||||
new UnityWebRequestPatch().Enable();
|
new UnityWebRequestPatch().Enable();
|
||||||
@ -26,6 +32,7 @@ namespace Aki.Core
|
|||||||
{
|
{
|
||||||
Logger.LogError($"A PATCH IN {GetType().Name} FAILED. SUBSEQUENT PATCHES HAVE NOT LOADED");
|
Logger.LogError($"A PATCH IN {GetType().Name} FAILED. SUBSEQUENT PATCHES HAVE NOT LOADED");
|
||||||
Logger.LogError($"{GetType().Name}: {ex}");
|
Logger.LogError($"{GetType().Name}: {ex}");
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,9 +5,6 @@ namespace Aki.Core.Models
|
|||||||
{
|
{
|
||||||
public class FakeCertificateHandler : CertificateHandler
|
public class FakeCertificateHandler : CertificateHandler
|
||||||
{
|
{
|
||||||
protected override bool ValidateCertificate(byte[] certificateData)
|
protected override bool ValidateCertificate(byte[] certificateData) => true;
|
||||||
{
|
|
||||||
return ValidationUtil.Validate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
using Aki.Core.Utils;
|
using Aki.Core.Utils;
|
||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Core.Patches
|
namespace Aki.Core.Patches
|
||||||
{
|
{
|
||||||
@ -11,11 +10,7 @@ namespace Aki.Core.Patches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
var methodName = "RunValidation";
|
return AccessTools.Method(typeof(BattleeyePatchClass), nameof(BattleeyePatchClass.RunValidation));
|
||||||
var flags = BindingFlags.Public | BindingFlags.Instance;
|
|
||||||
|
|
||||||
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
|
|
||||||
.GetMethod(methodName, flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
|
@ -12,8 +12,8 @@ namespace Aki.Core.Patches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return PatchConstants.FilesCheckerTypes.Single(x => x.Name == "ConsistencyController")
|
return PatchConstants.FilesCheckerTypes.SingleCustom(x => x.Name == "ConsistencyController")
|
||||||
.GetMethods().Single(x => x.Name == "EnsureConsistency" && x.ReturnType == typeof(Task<ICheckResult>));
|
.GetMethods().SingleCustom(x => x.Name == "EnsureConsistency" && x.ReturnType == typeof(Task<ICheckResult>));
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
|
@ -12,8 +12,8 @@ namespace Aki.Core.Patches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return PatchConstants.FilesCheckerTypes.Single(x => x.Name == "ConsistencyController")
|
return PatchConstants.FilesCheckerTypes.SingleCustom(x => x.Name == "ConsistencyController")
|
||||||
.GetMethods().Single(x => x.Name == "EnsureConsistencySingle" && x.ReturnType == typeof(Task<ICheckResult>));
|
.GetMethods().SingleCustom(x => x.Name == "EnsureConsistencySingle" && x.ReturnType == typeof(Task<ICheckResult>));
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Reflection.Emit;
|
|
||||||
using Aki.Core.Models;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Aki.Reflection.Patching;
|
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using FilesChecker;
|
|
||||||
using HarmonyLib;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Aki.Core.Patches
|
|
||||||
{
|
|
||||||
public class DataHandlerDebugPatch : ModulePatch
|
|
||||||
{
|
|
||||||
protected override MethodBase GetTargetMethod()
|
|
||||||
{
|
|
||||||
return PatchConstants.EftTypes
|
|
||||||
.Single(t => t.Name == "DataHandler")
|
|
||||||
.GetMethod("method_5", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
||||||
}
|
|
||||||
|
|
||||||
[PatchPostfix]
|
|
||||||
private static void PatchPrefix(ref string __result)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"response json: ${__result}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
35
project/Aki.Core/Patches/GameValidationPatch.cs
Normal file
35
project/Aki.Core/Patches/GameValidationPatch.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Aki.Common.Utils;
|
||||||
|
using Aki.Core.Utils;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Aki.Core.Patches
|
||||||
|
{
|
||||||
|
public class GameValidationPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private const string PluginName = "Aki.Core";
|
||||||
|
private const string ErrorMessage = "Validation failed";
|
||||||
|
private static BepInEx.Logging.ManualLogSource _logger = null;
|
||||||
|
private static bool _hasRun = false;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BattleeyePatchClass), nameof(BattleeyePatchClass.RunValidation));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix()
|
||||||
|
{
|
||||||
|
if (ValidationUtil.Validate() || _hasRun)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_logger == null)
|
||||||
|
_logger = BepInEx.Logging.Logger.CreateLogSource(PluginName);
|
||||||
|
|
||||||
|
_hasRun = true;
|
||||||
|
ServerLog.Warn($"Warning: {PluginName}", ErrorMessage);
|
||||||
|
_logger?.LogWarning(ErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,23 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using UnityEngine.Networking;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using Aki.Core.Utils;
|
using Aki.Core.Utils;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Core.Patches
|
namespace Aki.Core.Patches
|
||||||
{
|
{
|
||||||
public class SslCertificatePatch : ModulePatch
|
public class SslCertificatePatch : ModulePatch
|
||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return PatchConstants.EftTypes.Single(x => x.BaseType == typeof(CertificateHandler))
|
return AccessTools.Method(typeof(SslCertPatchClass), nameof(SslCertPatchClass.ValidateCertificate), new[] { typeof(X509Certificate) });
|
||||||
.GetMethod("ValidateCertificate", PatchConstants.PrivateFlags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static bool PatchPrefix(ref bool __result)
|
private static bool PatchPrefix(ref bool __result)
|
||||||
{
|
{
|
||||||
__result = ValidationUtil.Validate();
|
__result = true;
|
||||||
return false; // Skip origial
|
return false; // Skip original
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,7 @@ namespace Aki.Core.Patches
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = GClass239.DEBUG_LOGIC; // UPDATE BELOW LINE TOO
|
var type = PatchConstants.EftTypes.SingleOrDefault(t => t.GetField("TransportPrefixes") != null);
|
||||||
var type = PatchConstants.EftTypes.Single(t => t.Name == "Class239");
|
|
||||||
|
|
||||||
if (type == null)
|
if (type == null)
|
||||||
{
|
{
|
||||||
@ -36,18 +35,17 @@ namespace Aki.Core.Patches
|
|||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return PatchConstants.EftTypes.Single(t => t.GetMethods().Any(m => m.Name == "CreateFromLegacyParams"))
|
return PatchConstants.EftTypes.SingleCustom(t => t.GetMethods().Any(m => m.Name == "CreateFromLegacyParams"))
|
||||||
.GetMethod("CreateFromLegacyParams", BindingFlags.Static | BindingFlags.Public);
|
.GetMethod("CreateFromLegacyParams", BindingFlags.Static | BindingFlags.Public);
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static bool PatchPrefix(ref GStruct21 legacyParams)
|
private static bool PatchPrefix(ref LegacyParamsStruct legacyParams)
|
||||||
{
|
{
|
||||||
//Console.WriteLine($"Original url {legacyParams.Url}");
|
|
||||||
legacyParams.Url = legacyParams.Url
|
legacyParams.Url = legacyParams.Url
|
||||||
.Replace("https://", "")
|
.Replace("https://", "")
|
||||||
.Replace("http://", "");
|
.Replace("http://", "");
|
||||||
//Console.WriteLine($"Edited url {legacyParams.Url}");
|
|
||||||
return true; // do original method after
|
return true; // do original method after
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
using Aki.Reflection.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Aki.Core.Patches
|
namespace Aki.Core.Patches
|
||||||
@ -10,15 +9,18 @@ namespace Aki.Core.Patches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
var targetInterface = PatchConstants.EftTypes.Single(x => x == typeof(IConnectionHandler) && x.IsInterface);
|
var targetInterface = PatchConstants.EftTypes.SingleCustom(x => x == typeof(IConnectionHandler) && x.IsInterface);
|
||||||
var typeThatMatches = PatchConstants.EftTypes.Single(x => targetInterface.IsAssignableFrom(x) && x.IsAbstract && !x.IsInterface);
|
var typeThatMatches = PatchConstants.EftTypes.SingleCustom(x => targetInterface.IsAssignableFrom(x) && x.IsAbstract && !x.IsInterface);
|
||||||
return typeThatMatches.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Single(x => x.ReturnType == typeof(Uri));
|
|
||||||
|
return typeThatMatches.GetMethods(BindingFlags.Public | BindingFlags.Instance).SingleCustom(x => x.ReturnType == typeof(Uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is a pass through postfix and behaves a little differently than usual
|
||||||
|
// https://harmony.pardeike.net/articles/patching-postfix.html#pass-through-postfixes
|
||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
private static Uri PatchPostfix(Uri __instance)
|
private static Uri PatchPostfix(Uri __result)
|
||||||
{
|
{
|
||||||
return new Uri(__instance.ToString().Replace("wss:", "ws:"));
|
return new Uri(__result.ToString().Replace("wss:", "ws:"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,12 @@ namespace Aki.Core.Utils
|
|||||||
var v1 = Registry.LocalMachine.OpenSubKey(c0, false).GetValue("InstallLocation");
|
var v1 = Registry.LocalMachine.OpenSubKey(c0, false).GetValue("InstallLocation");
|
||||||
var v2 = (v1 != null) ? v1.ToString() : string.Empty;
|
var v2 = (v1 != null) ? v1.ToString() : string.Empty;
|
||||||
var v3 = new DirectoryInfo(v2);
|
var v3 = new DirectoryInfo(v2);
|
||||||
|
|
||||||
var v4 = new FileSystemInfo[]
|
var v4 = new FileSystemInfo[]
|
||||||
{
|
{
|
||||||
v3,
|
v3,
|
||||||
new FileInfo(Path.Combine(v2, @"BattlEye\BEClient_x64.dll")),
|
new FileInfo(Path.Combine(v2, @"BattlEye\BEClient_x64.dll")),
|
||||||
new FileInfo(Path.Combine(v2, @"BattlEye\BEService_x64.dll")),
|
new FileInfo(Path.Combine(v2, @"BattlEye\BEService_x64.exe")),
|
||||||
new FileInfo(Path.Combine(v2, "ConsistencyInfo")),
|
new FileInfo(Path.Combine(v2, "ConsistencyInfo")),
|
||||||
new FileInfo(Path.Combine(v2, "Uninstall.exe")),
|
new FileInfo(Path.Combine(v2, "Uninstall.exe")),
|
||||||
new FileInfo(Path.Combine(v2, "UnityCrashHandler64.exe"))
|
new FileInfo(Path.Combine(v2, "UnityCrashHandler64.exe"))
|
||||||
@ -55,4 +55,4 @@ namespace Aki.Core.Utils
|
|||||||
return File.Exists(a) ? new FileInfo(a) : null;
|
return File.Exists(a) ? new FileInfo(a) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ namespace Aki.Custom.Airdrops.Utils
|
|||||||
{
|
{
|
||||||
var serverConfig = GetConfigFromServer();
|
var serverConfig = GetConfigFromServer();
|
||||||
var allAirdropPoints = LocationScene.GetAll<AirdropPoint>().ToList();
|
var allAirdropPoints = LocationScene.GetAll<AirdropPoint>().ToList();
|
||||||
var playerPosition = gameWorld.MainPlayer.Position;
|
var playerPosition = ((IPlayer)gameWorld.MainPlayer).Position;
|
||||||
var flareAirdropPoints = new List<AirdropPoint>();
|
var flareAirdropPoints = new List<AirdropPoint>();
|
||||||
var dropChance = ChanceToSpawn(gameWorld, serverConfig, isFlare);
|
var dropChance = ChanceToSpawn(gameWorld, serverConfig, isFlare);
|
||||||
var flareSpawnRadiusDistance = 100f;
|
var flareSpawnRadiusDistance = 100f;
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net471</TargetFramework>
|
||||||
<AssemblyName>aki-custom</AssemblyName>
|
<AssemblyName>aki-custom</AssemblyName>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Company>Aki</Company>
|
<Company>Aki</Company>
|
||||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
<Copyright>Copyright @ Aki 2024</Copyright>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="AnimationSystem.Types" HintPath="..\Shared\Managed\AnimationSystem.Types.dll" />
|
||||||
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\hollowed.dll" Private="False" />
|
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\hollowed.dll" Private="False" />
|
||||||
<Reference Include="Comfort" HintPath="..\Shared\Managed\Comfort.dll" Private="False" />
|
<Reference Include="Comfort" HintPath="..\Shared\Managed\Comfort.dll" Private="False" />
|
||||||
|
<Reference Include="DissonanceVoip" HintPath="..\Shared\Managed\DissonanceVoip.dll" Private="False" />
|
||||||
<Reference Include="Sirenix.Serialization" HintPath="..\Shared\Managed\Sirenix.Serialization.dll" Private="False" />
|
<Reference Include="Sirenix.Serialization" HintPath="..\Shared\Managed\Sirenix.Serialization.dll" Private="False" />
|
||||||
|
<Reference Include="Unity.TextMeshPro" HintPath="..\Shared\Managed\Unity.TextMeshPro.dll" Private="False" />
|
||||||
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||||
<Reference Include="UnityEngine.AIModule" HintPath="..\Shared\Managed\UnityEngine.AIModule.dll" Private="False" />
|
<Reference Include="UnityEngine.AIModule" HintPath="..\Shared\Managed\UnityEngine.AIModule.dll" Private="False" />
|
||||||
<Reference Include="UnityEngine.AnimationModule" HintPath="..\Shared\Managed\UnityEngine.AnimationModule.dll" Private="False" />
|
<Reference Include="UnityEngine.AnimationModule" HintPath="..\Shared\Managed\UnityEngine.AnimationModule.dll" Private="False" />
|
||||||
<Reference Include="UnityEngine.AssetBundleModule" HintPath="..\Shared\Managed\UnityEngine.AssetBundleModule.dll" Private="False" />
|
<Reference Include="UnityEngine.AssetBundleModule" HintPath="..\Shared\Managed\UnityEngine.AssetBundleModule.dll" Private="False" />
|
||||||
<Reference Include="UnityEngine.AudioModule" HintPath="..\Shared\Managed\UnityEngine.AudioModule.dll" Private="False" />
|
<Reference Include="UnityEngine.AudioModule" HintPath="..\Shared\Managed\UnityEngine.AudioModule.dll" Private="False" />
|
||||||
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||||
@ -38,6 +42,7 @@
|
|||||||
<ProjectReference Include="..\Aki.PrePatch\Aki.PrePatch.csproj" />
|
<ProjectReference Include="..\Aki.PrePatch\Aki.PrePatch.csproj" />
|
||||||
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||||
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.SinglePlayer\Aki.SinglePlayer.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using Aki.Common;
|
using Aki.Common;
|
||||||
using Aki.Custom.Airdrops.Patches;
|
using Aki.Custom.Airdrops.Patches;
|
||||||
|
using Aki.Custom.BTR.Patches;
|
||||||
using Aki.Custom.Patches;
|
using Aki.Custom.Patches;
|
||||||
using Aki.Custom.Utils;
|
using Aki.Custom.Utils;
|
||||||
using Aki.SinglePlayer.Patches.ScavMode;
|
using Aki.Reflection.Utils;
|
||||||
|
using Aki.SinglePlayer.Utils.MainMenu;
|
||||||
using BepInEx;
|
using BepInEx;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Aki.Custom
|
namespace Aki.Custom
|
||||||
{
|
{
|
||||||
@ -29,9 +32,12 @@ namespace Aki.Custom
|
|||||||
// Fixed in live, no need for patch
|
// Fixed in live, no need for patch
|
||||||
//new RaidSettingsWindowPatch().Enable();
|
//new RaidSettingsWindowPatch().Enable();
|
||||||
new OfflineRaidSettingsMenuPatch().Enable();
|
new OfflineRaidSettingsMenuPatch().Enable();
|
||||||
new SessionIdPatch().Enable();
|
// new SessionIdPatch().Enable();
|
||||||
new VersionLabelPatch().Enable();
|
new VersionLabelPatch().Enable();
|
||||||
new IsEnemyPatch().Enable();
|
new IsEnemyPatch().Enable();
|
||||||
|
new BotCalledDataTryCallPatch().Enable();
|
||||||
|
new BotCallForHelpCallBotPatch().Enable();
|
||||||
|
new BotOwnerDisposePatch().Enable();
|
||||||
new LocationLootCacheBustingPatch().Enable();
|
new LocationLootCacheBustingPatch().Enable();
|
||||||
//new AddSelfAsEnemyPatch().Enable();
|
//new AddSelfAsEnemyPatch().Enable();
|
||||||
new CheckAndAddEnemyPatch().Enable();
|
new CheckAndAddEnemyPatch().Enable();
|
||||||
@ -41,18 +47,45 @@ namespace Aki.Custom
|
|||||||
new AirdropFlarePatch().Enable();
|
new AirdropFlarePatch().Enable();
|
||||||
new AddSptBotSettingsPatch().Enable();
|
new AddSptBotSettingsPatch().Enable();
|
||||||
new CustomAiPatch().Enable();
|
new CustomAiPatch().Enable();
|
||||||
|
new AddTraitorScavsPatch().Enable();
|
||||||
new ExitWhileLootingPatch().Enable();
|
new ExitWhileLootingPatch().Enable();
|
||||||
new QTEPatch().Enable();
|
new QTEPatch().Enable();
|
||||||
new PmcFirstAidPatch().Enable();
|
new PmcFirstAidPatch().Enable();
|
||||||
new SettingsLocationPatch().Enable();
|
new SettingsLocationPatch().Enable();
|
||||||
|
new SetLocationIdOnRaidStartPatch().Enable();
|
||||||
//new RankPanelPatch().Enable();
|
//new RankPanelPatch().Enable();
|
||||||
new RagfairFeePatch().Enable();
|
new RagfairFeePatch().Enable();
|
||||||
new ScavQuestPatch().Enable();
|
new ScavQuestPatch().Enable();
|
||||||
|
new FixBrokenSpawnOnSandboxPatch().Enable();
|
||||||
|
new BTRPathLoadPatch().Enable();
|
||||||
|
new BTRActivateTraderDialogPatch().Enable();
|
||||||
|
new BTRInteractionPatch().Enable();
|
||||||
|
new BTRExtractPassengersPatch().Enable();
|
||||||
|
new BTRBotAttachPatch().Enable();
|
||||||
|
new BTRReceiveDamageInfoPatch().Enable();
|
||||||
|
new BTRTurretCanShootPatch().Enable();
|
||||||
|
new BTRTurretDefaultAimingPositionPatch().Enable();
|
||||||
|
new BTRIsDoorsClosedPath().Enable();
|
||||||
|
new BTRPatch().Enable();
|
||||||
|
new BTRTransferItemsPatch().Enable();
|
||||||
|
new BTREndRaidItemDeliveryPatch().Enable();
|
||||||
|
new BTRDestroyAtRaidEndPatch().Enable();
|
||||||
|
new BTRVehicleMovementSpeedPatch().Enable();
|
||||||
|
new ScavItemCheckmarkPatch().Enable();
|
||||||
|
new ResetTraderServicesPatch().Enable();
|
||||||
|
new CultistAmuletRemovalPatch().Enable();
|
||||||
|
new HalloweenExtractPatch().Enable();
|
||||||
|
new ClampRagdollPatch().Enable();
|
||||||
|
|
||||||
|
HookObject.AddOrGetComponent<MenuNotificationManager>();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError($"A PATCH IN {GetType().Name} FAILED. SUBSEQUENT PATCHES HAVE NOT LOADED");
|
Logger.LogError($"A PATCH IN {GetType().Name} FAILED. SUBSEQUENT PATCHES HAVE NOT LOADED");
|
||||||
Logger.LogError($"{GetType().Name}: {ex}");
|
Logger.LogError($"{GetType().Name}: {ex}");
|
||||||
|
MessageBoxHelper.Show($"A patch in {GetType().Name} FAILED. {ex.Message}. SUBSEQUENT PATCHES HAVE NOT LOADED, CHECK LOG FOR MORE DETAILS", "ERROR", MessageBoxHelper.MessageBoxType.OK);
|
||||||
|
Application.Quit();
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
518
project/Aki.Custom/BTR/BTRManager.cs
Normal file
518
project/Aki.Custom/BTR/BTRManager.cs
Normal file
@ -0,0 +1,518 @@
|
|||||||
|
using Aki.Custom.BTR.Utils;
|
||||||
|
using Aki.SinglePlayer.Utils.TraderServices;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
using EFT.UI;
|
||||||
|
using EFT.Vehicle;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using Random = UnityEngine.Random;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR
|
||||||
|
{
|
||||||
|
public class BTRManager : MonoBehaviour
|
||||||
|
{
|
||||||
|
private GameWorld gameWorld;
|
||||||
|
private BotEventHandler botEventHandler;
|
||||||
|
|
||||||
|
private BotBTRService btrBotService;
|
||||||
|
private BTRControllerClass btrController;
|
||||||
|
private BTRVehicle btrServerSide;
|
||||||
|
private BTRView btrClientSide;
|
||||||
|
private BotOwner btrBotShooter;
|
||||||
|
private BTRDataPacket btrDataPacket = default;
|
||||||
|
private bool btrBotShooterInitialized = false;
|
||||||
|
|
||||||
|
private float coverFireTime = 90f;
|
||||||
|
private Coroutine _coverFireTimerCoroutine;
|
||||||
|
|
||||||
|
private BTRSide lastInteractedBtrSide;
|
||||||
|
public BTRSide LastInteractedBtrSide => lastInteractedBtrSide;
|
||||||
|
|
||||||
|
private Coroutine _shootingTargetCoroutine;
|
||||||
|
private BTRTurretServer btrTurretServer;
|
||||||
|
private bool isTurretInDefaultRotation;
|
||||||
|
private EnemyInfo currentTarget = null;
|
||||||
|
private bool isShooting = false;
|
||||||
|
private float machineGunAimDelay = 0.4f;
|
||||||
|
private Vector2 machineGunBurstCount;
|
||||||
|
private Vector2 machineGunRecoveryTime;
|
||||||
|
private BulletClass btrMachineGunAmmo;
|
||||||
|
private Item btrMachineGunWeapon;
|
||||||
|
private Player.FirearmController firearmController;
|
||||||
|
private WeaponSoundPlayer weaponSoundPlayer;
|
||||||
|
|
||||||
|
private MethodInfo _updateTaxiPriceMethod;
|
||||||
|
|
||||||
|
private float originalDamageCoeff;
|
||||||
|
|
||||||
|
BTRManager()
|
||||||
|
{
|
||||||
|
Type btrControllerType = typeof(BTRControllerClass);
|
||||||
|
_updateTaxiPriceMethod = AccessTools.GetDeclaredMethods(btrControllerType).Single(IsUpdateTaxiPriceMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
if (gameWorld == null)
|
||||||
|
{
|
||||||
|
Destroy(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameWorld.BtrController == null)
|
||||||
|
{
|
||||||
|
gameWorld.BtrController = new BTRControllerClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
btrController = gameWorld.BtrController;
|
||||||
|
|
||||||
|
InitBtr();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ConsoleScreen.LogError("[AKI-BTR] Unable to spawn BTR. Check logs.");
|
||||||
|
Destroy(this);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPlayerInteractDoor(PlayerInteractPacket interactPacket)
|
||||||
|
{
|
||||||
|
btrServerSide.LeftSlot0State = 0;
|
||||||
|
btrServerSide.LeftSlot1State = 0;
|
||||||
|
btrServerSide.RightSlot0State = 0;
|
||||||
|
btrServerSide.RightSlot1State = 0;
|
||||||
|
|
||||||
|
bool playerGoIn = interactPacket.InteractionType == EInteractionType.GoIn;
|
||||||
|
|
||||||
|
if (interactPacket.SideId == 0 && playerGoIn)
|
||||||
|
{
|
||||||
|
if (interactPacket.SlotId == 0)
|
||||||
|
{
|
||||||
|
btrServerSide.LeftSlot0State = 1;
|
||||||
|
}
|
||||||
|
else if (interactPacket.SlotId == 1)
|
||||||
|
{
|
||||||
|
btrServerSide.LeftSlot1State = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (interactPacket.SideId == 1 && playerGoIn)
|
||||||
|
{
|
||||||
|
if (interactPacket.SlotId == 0)
|
||||||
|
{
|
||||||
|
btrServerSide.RightSlot0State = 1;
|
||||||
|
}
|
||||||
|
else if (interactPacket.SlotId == 1)
|
||||||
|
{
|
||||||
|
btrServerSide.RightSlot1State = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the player is going into the BTR, store their damage coefficient
|
||||||
|
// and set it to 0, so they don't die while inside the BTR
|
||||||
|
if (interactPacket.InteractionType == EInteractionType.GoIn)
|
||||||
|
{
|
||||||
|
originalDamageCoeff = gameWorld.MainPlayer.ActiveHealthController.DamageCoeff;
|
||||||
|
gameWorld.MainPlayer.ActiveHealthController.SetDamageCoeff(0f);
|
||||||
|
|
||||||
|
}
|
||||||
|
// Otherwise restore the damage coefficient
|
||||||
|
else if (interactPacket.InteractionType == EInteractionType.GoOut)
|
||||||
|
{
|
||||||
|
gameWorld.MainPlayer.ActiveHealthController.SetDamageCoeff(originalDamageCoeff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find `BTRControllerClass.method_9(PathDestination currentDestinationPoint, bool lastRoutePoint)`
|
||||||
|
private bool IsUpdateTaxiPriceMethod(MethodInfo method)
|
||||||
|
{
|
||||||
|
return (method.GetParameters().Length == 2 && method.GetParameters()[0].ParameterType == typeof(PathDestination));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
btrController.SyncBTRVehicleFromServer(UpdateDataPacket());
|
||||||
|
|
||||||
|
if (btrController.BotShooterBtr == null) return;
|
||||||
|
|
||||||
|
// BotShooterBtr doesn't get assigned to BtrController immediately so we check this in Update
|
||||||
|
if (!btrBotShooterInitialized)
|
||||||
|
{
|
||||||
|
InitBtrBotService();
|
||||||
|
btrBotShooterInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTarget();
|
||||||
|
|
||||||
|
if (HasTarget())
|
||||||
|
{
|
||||||
|
SetAim();
|
||||||
|
|
||||||
|
if (!isShooting && CanShoot())
|
||||||
|
{
|
||||||
|
StartShooting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isTurretInDefaultRotation)
|
||||||
|
{
|
||||||
|
btrTurretServer.DisableAiming();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitBtr()
|
||||||
|
{
|
||||||
|
// Initial setup
|
||||||
|
botEventHandler = Singleton<BotEventHandler>.Instance;
|
||||||
|
var botsController = Singleton<IBotGame>.Instance.BotsController;
|
||||||
|
btrBotService = botsController.BotTradersServices.BTRServices;
|
||||||
|
btrController.method_3(); // spawns server-side BTR game object
|
||||||
|
botsController.BotSpawner.SpawnBotBTR(); // spawns the scav bot which controls the BTR's turret
|
||||||
|
|
||||||
|
// Initial BTR configuration
|
||||||
|
btrServerSide = btrController.BtrVehicle;
|
||||||
|
btrClientSide = btrController.BtrView;
|
||||||
|
btrServerSide.transform.Find("KillBox").gameObject.AddComponent<BTRRoadKillTrigger>();
|
||||||
|
|
||||||
|
// Get config from server and initialise respective settings
|
||||||
|
ConfigureSettingsFromServer();
|
||||||
|
|
||||||
|
var btrMapConfig = btrController.MapPathsConfiguration;
|
||||||
|
btrServerSide.CurrentPathConfig = btrMapConfig.PathsConfiguration.pathsConfigurations.RandomElement();
|
||||||
|
btrServerSide.Initialization(btrMapConfig);
|
||||||
|
btrController.method_14(); // creates and assigns the BTR a fake stash
|
||||||
|
|
||||||
|
DisableServerSideRenderers();
|
||||||
|
|
||||||
|
gameWorld.MainPlayer.OnBtrStateChanged += HandleBtrDoorState;
|
||||||
|
|
||||||
|
btrServerSide.MoveEnable();
|
||||||
|
btrServerSide.IncomingToDestinationEvent += ToDestinationEvent;
|
||||||
|
|
||||||
|
// Sync initial position and rotation
|
||||||
|
UpdateDataPacket();
|
||||||
|
btrClientSide.transform.position = btrDataPacket.position;
|
||||||
|
btrClientSide.transform.rotation = btrDataPacket.rotation;
|
||||||
|
|
||||||
|
// Initialise turret variables
|
||||||
|
btrTurretServer = btrServerSide.BTRTurret;
|
||||||
|
var btrTurretDefaultTargetTransform = (Transform)AccessTools.Field(btrTurretServer.GetType(), "defaultTargetTransform").GetValue(btrTurretServer);
|
||||||
|
isTurretInDefaultRotation = btrTurretServer.targetTransform == btrTurretDefaultTargetTransform
|
||||||
|
&& btrTurretServer.targetPosition == btrTurretServer.defaultAimingPosition;
|
||||||
|
btrMachineGunAmmo = (BulletClass)BTRUtil.CreateItem(BTRUtil.BTRMachineGunAmmoTplId);
|
||||||
|
btrMachineGunWeapon = BTRUtil.CreateItem(BTRUtil.BTRMachineGunWeaponTplId);
|
||||||
|
|
||||||
|
// Pull services data for the BTR from the server
|
||||||
|
TraderServicesManager.Instance.GetTraderServicesDataFromServer(BTRUtil.BTRTraderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureSettingsFromServer()
|
||||||
|
{
|
||||||
|
var serverConfig = BTRUtil.GetConfigFromServer();
|
||||||
|
|
||||||
|
btrServerSide.moveSpeed = serverConfig.MoveSpeed;
|
||||||
|
btrServerSide.pauseDurationRange.x = serverConfig.PointWaitTime.Min;
|
||||||
|
btrServerSide.pauseDurationRange.y = serverConfig.PointWaitTime.Max;
|
||||||
|
btrServerSide.readyToDeparture = serverConfig.TaxiWaitTime;
|
||||||
|
coverFireTime = serverConfig.CoverFireTime;
|
||||||
|
machineGunAimDelay = serverConfig.MachineGunAimDelay;
|
||||||
|
machineGunBurstCount = new Vector2(serverConfig.MachineGunBurstCount.Min, serverConfig.MachineGunBurstCount.Max);
|
||||||
|
machineGunRecoveryTime = new Vector2(serverConfig.MachineGunRecoveryTime.Min, serverConfig.MachineGunRecoveryTime.Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitBtrBotService()
|
||||||
|
{
|
||||||
|
btrBotShooter = btrController.BotShooterBtr;
|
||||||
|
firearmController = btrBotShooter.GetComponent<Player.FirearmController>();
|
||||||
|
var weaponPrefab = (WeaponPrefab)AccessTools.Field(firearmController.GetType(), "weaponPrefab_0").GetValue(firearmController);
|
||||||
|
weaponSoundPlayer = weaponPrefab.GetComponent<WeaponSoundPlayer>();
|
||||||
|
|
||||||
|
btrBotService.Reset(); // Player will be added to Neutrals list and removed from Enemies list
|
||||||
|
TraderServicesManager.Instance.OnTraderServicePurchased += BtrTraderServicePurchased;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BTR has arrived at a destination, re-calculate taxi prices and remove purchased taxi service
|
||||||
|
*/
|
||||||
|
private void ToDestinationEvent(PathDestination destinationPoint, bool isFirst, bool isFinal, bool isLastRoutePoint)
|
||||||
|
{
|
||||||
|
// Remove purchased taxi service
|
||||||
|
TraderServicesManager.Instance.RemovePurchasedService(ETraderServiceType.PlayerTaxi, BTRUtil.BTRTraderId);
|
||||||
|
|
||||||
|
// Update the prices for the taxi service
|
||||||
|
_updateTaxiPriceMethod.Invoke(btrController, new object[] { destinationPoint, isFinal });
|
||||||
|
|
||||||
|
// Update the UI
|
||||||
|
TraderServicesManager.Instance.GetTraderServicesDataFromServer(BTRUtil.BTRTraderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsBtrService(ETraderServiceType serviceType)
|
||||||
|
{
|
||||||
|
if (serviceType == ETraderServiceType.BtrItemsDelivery
|
||||||
|
|| serviceType == ETraderServiceType.PlayerTaxi
|
||||||
|
|| serviceType == ETraderServiceType.BtrBotCover)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtrTraderServicePurchased(ETraderServiceType serviceType, string subserviceId)
|
||||||
|
{
|
||||||
|
if (!IsBtrService(serviceType))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Player> passengers = gameWorld.AllAlivePlayersList.Where(x => x.BtrState == EPlayerBtrState.Inside).ToList();
|
||||||
|
List<int> playersToNotify = passengers.Select(x => x.Id).ToList();
|
||||||
|
btrController.method_6(playersToNotify, serviceType); // notify BTR passengers that a service has been purchased
|
||||||
|
|
||||||
|
switch (serviceType)
|
||||||
|
{
|
||||||
|
case ETraderServiceType.BtrBotCover:
|
||||||
|
botEventHandler.ApplyTraderServiceBtrSupport(passengers);
|
||||||
|
StartCoverFireTimer(coverFireTime);
|
||||||
|
break;
|
||||||
|
case ETraderServiceType.PlayerTaxi:
|
||||||
|
btrController.BtrVehicle.IsPaid = true;
|
||||||
|
btrController.BtrVehicle.MoveToDestination(subserviceId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartCoverFireTimer(float time)
|
||||||
|
{
|
||||||
|
_coverFireTimerCoroutine = StaticManager.BeginCoroutine(CoverFireTimer(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator CoverFireTimer(float time)
|
||||||
|
{
|
||||||
|
yield return new WaitForSecondsRealtime(time);
|
||||||
|
botEventHandler.StopTraderServiceBtrSupport();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleBtrDoorState(EPlayerBtrState playerBtrState)
|
||||||
|
{
|
||||||
|
if (playerBtrState == EPlayerBtrState.GoIn || playerBtrState == EPlayerBtrState.GoOut)
|
||||||
|
{
|
||||||
|
// Open Door
|
||||||
|
UpdateBTRSideDoorState(1);
|
||||||
|
}
|
||||||
|
else if (playerBtrState == EPlayerBtrState.Inside || playerBtrState == EPlayerBtrState.Outside)
|
||||||
|
{
|
||||||
|
// Close Door
|
||||||
|
UpdateBTRSideDoorState(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBTRSideDoorState(byte state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var player = gameWorld.MainPlayer;
|
||||||
|
|
||||||
|
BTRSide btrSide = player.BtrInteractionSide != null ? player.BtrInteractionSide : lastInteractedBtrSide;
|
||||||
|
byte sideId = btrClientSide.GetSideId(btrSide);
|
||||||
|
switch (sideId)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
btrServerSide.LeftSideState = state;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
btrServerSide.RightSideState = state;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastInteractedBtrSide = player.BtrInteractionSide;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ConsoleScreen.LogError("[AKI-BTR] lastInteractedBtrSide is null when it shouldn't be. Check logs.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BTRDataPacket UpdateDataPacket()
|
||||||
|
{
|
||||||
|
btrDataPacket.position = btrServerSide.transform.position;
|
||||||
|
btrDataPacket.rotation = btrServerSide.transform.rotation;
|
||||||
|
if (btrTurretServer != null && btrTurretServer.gunsBlockRoot != null)
|
||||||
|
{
|
||||||
|
btrDataPacket.turretRotation = btrTurretServer.transform.rotation;
|
||||||
|
btrDataPacket.gunsBlockRotation = btrTurretServer.gunsBlockRoot.rotation;
|
||||||
|
}
|
||||||
|
btrDataPacket.State = (byte)btrServerSide.BtrState;
|
||||||
|
btrDataPacket.RouteState = (byte)btrServerSide.VehicleRouteState;
|
||||||
|
btrDataPacket.LeftSideState = btrServerSide.LeftSideState;
|
||||||
|
btrDataPacket.LeftSlot0State = btrServerSide.LeftSlot0State;
|
||||||
|
btrDataPacket.LeftSlot1State = btrServerSide.LeftSlot1State;
|
||||||
|
btrDataPacket.RightSideState = btrServerSide.RightSideState;
|
||||||
|
btrDataPacket.RightSlot0State = btrServerSide.RightSlot0State;
|
||||||
|
btrDataPacket.RightSlot1State = btrServerSide.RightSlot1State;
|
||||||
|
btrDataPacket.currentSpeed = btrServerSide.currentSpeed;
|
||||||
|
btrDataPacket.timeToEndPause = btrServerSide.timeToEndPause;
|
||||||
|
btrDataPacket.moveDirection = (byte)btrServerSide.VehicleMoveDirection;
|
||||||
|
btrDataPacket.MoveSpeed = btrServerSide.moveSpeed;
|
||||||
|
if (btrController != null && btrController.BotShooterBtr != null)
|
||||||
|
{
|
||||||
|
btrDataPacket.BtrBotId = btrController.BotShooterBtr.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return btrDataPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisableServerSideRenderers()
|
||||||
|
{
|
||||||
|
var meshRenderers = btrServerSide.transform.GetComponentsInChildren<MeshRenderer>();
|
||||||
|
foreach (var renderer in meshRenderers)
|
||||||
|
{
|
||||||
|
renderer.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
btrServerSide.turnCheckerObject.GetComponent<Renderer>().enabled = false; // Disables the red debug sphere
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTarget()
|
||||||
|
{
|
||||||
|
currentTarget = btrBotShooter.Memory.GoalEnemy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasTarget()
|
||||||
|
{
|
||||||
|
if (currentTarget != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetAim()
|
||||||
|
{
|
||||||
|
if (currentTarget.IsVisible)
|
||||||
|
{
|
||||||
|
Vector3 targetPos = currentTarget.CurrPosition;
|
||||||
|
Transform targetTransform = currentTarget.Person.Transform.Original;
|
||||||
|
if (btrTurretServer.CheckPositionInAimingZone(targetPos) && btrTurretServer.targetTransform != targetTransform)
|
||||||
|
{
|
||||||
|
btrTurretServer.EnableAimingObject(targetTransform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Vector3 targetLastPos = currentTarget.EnemyLastPositionReal;
|
||||||
|
if (btrTurretServer.CheckPositionInAimingZone(targetLastPos)
|
||||||
|
&& Time.time - currentTarget.PersonalLastSeenTime < 3f
|
||||||
|
&& btrTurretServer.targetPosition != targetLastPos)
|
||||||
|
{
|
||||||
|
btrTurretServer.EnableAimingPosition(targetLastPos);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (Time.time - currentTarget.PersonalLastSeenTime >= 3f && !isTurretInDefaultRotation)
|
||||||
|
{
|
||||||
|
btrTurretServer.DisableAiming();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanShoot()
|
||||||
|
{
|
||||||
|
if (currentTarget.IsVisible && btrBotShooter.BotBtrData.CanShoot())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartShooting()
|
||||||
|
{
|
||||||
|
_shootingTargetCoroutine = StaticManager.BeginCoroutine(ShootMachineGun());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom method to make the BTR coaxial machine gun shoot.
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerator ShootMachineGun()
|
||||||
|
{
|
||||||
|
isShooting = true;
|
||||||
|
|
||||||
|
yield return new WaitForSecondsRealtime(machineGunAimDelay);
|
||||||
|
if (currentTarget?.Person == null || currentTarget?.IsVisible == false || !btrBotShooter.BotBtrData.CanShoot())
|
||||||
|
{
|
||||||
|
isShooting = false;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform machineGunMuzzle = btrTurretServer.machineGunLaunchPoint;
|
||||||
|
var ballisticCalculator = gameWorld.SharedBallisticsCalculator;
|
||||||
|
|
||||||
|
int burstMin = Mathf.FloorToInt(machineGunBurstCount.x);
|
||||||
|
int burstMax = Mathf.FloorToInt(machineGunBurstCount.y);
|
||||||
|
int burstCount = Random.Range(burstMin, burstMax + 1);
|
||||||
|
Vector3 targetHeadPos = currentTarget.Person.PlayerBones.Head.position;
|
||||||
|
while (burstCount > 0)
|
||||||
|
{
|
||||||
|
// Only update shooting position if the target isn't null
|
||||||
|
if (currentTarget?.Person != null)
|
||||||
|
{
|
||||||
|
targetHeadPos = currentTarget.Person.PlayerBones.Head.position;
|
||||||
|
}
|
||||||
|
Vector3 aimDirection = Vector3.Normalize(targetHeadPos - machineGunMuzzle.position);
|
||||||
|
ballisticCalculator.Shoot(btrMachineGunAmmo, machineGunMuzzle.position, aimDirection, btrBotShooter.ProfileId, btrMachineGunWeapon, 1f, 0);
|
||||||
|
firearmController.method_54(weaponSoundPlayer, btrMachineGunAmmo, machineGunMuzzle.position, aimDirection, false);
|
||||||
|
burstCount--;
|
||||||
|
yield return new WaitForSecondsRealtime(0.092308f); // 650 RPM
|
||||||
|
}
|
||||||
|
|
||||||
|
float waitTime = Random.Range(machineGunRecoveryTime.x, machineGunRecoveryTime.y);
|
||||||
|
yield return new WaitForSecondsRealtime(waitTime);
|
||||||
|
|
||||||
|
isShooting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (gameWorld == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticManager.KillCoroutine(ref _shootingTargetCoroutine);
|
||||||
|
StaticManager.KillCoroutine(ref _coverFireTimerCoroutine);
|
||||||
|
|
||||||
|
if (TraderServicesManager.Instance != null)
|
||||||
|
{
|
||||||
|
TraderServicesManager.Instance.OnTraderServicePurchased -= BtrTraderServicePurchased;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameWorld.MainPlayer != null)
|
||||||
|
{
|
||||||
|
gameWorld.MainPlayer.OnBtrStateChanged -= HandleBtrDoorState;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btrClientSide != null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[AKI-BTR] BTRManager - Destroying btrClientSide");
|
||||||
|
Destroy(btrClientSide.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btrServerSide != null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[AKI-BTR] BTRManager - Destroying btrServerSide");
|
||||||
|
Destroy(btrServerSide.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
project/Aki.Custom/BTR/BTRRoadKillTrigger.cs
Normal file
38
project/Aki.Custom/BTR/BTRRoadKillTrigger.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using EFT;
|
||||||
|
using EFT.Interactive;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR
|
||||||
|
{
|
||||||
|
public class BTRRoadKillTrigger : DamageTrigger
|
||||||
|
{
|
||||||
|
public override bool IsStatic => false;
|
||||||
|
|
||||||
|
public override void AddPenalty(GInterface94 player)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PlaySound()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProceedDamage(GInterface94 player, BodyPartCollider bodyPart)
|
||||||
|
{
|
||||||
|
bodyPart.ApplyInstantKill(new DamageInfo()
|
||||||
|
{
|
||||||
|
Damage = 9999f,
|
||||||
|
Direction = Vector3.zero,
|
||||||
|
HitCollider = bodyPart.Collider,
|
||||||
|
HitNormal = Vector3.zero,
|
||||||
|
HitPoint = Vector3.zero,
|
||||||
|
DamageType = EDamageType.Btr,
|
||||||
|
HittedBallisticCollider = bodyPart,
|
||||||
|
Player = null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RemovePenalty(GInterface94 player)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
project/Aki.Custom/BTR/Models/BtrConfigModel.cs
Normal file
38
project/Aki.Custom/BTR/Models/BtrConfigModel.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Models
|
||||||
|
{
|
||||||
|
public class BTRConfigModel
|
||||||
|
{
|
||||||
|
[JsonProperty("moveSpeed")]
|
||||||
|
public float MoveSpeed { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("coverFireTime")]
|
||||||
|
public float CoverFireTime { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("pointWaitTime")]
|
||||||
|
public BtrMinMaxValue PointWaitTime { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("taxiWaitTime")]
|
||||||
|
public float TaxiWaitTime { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("machineGunAimDelay")]
|
||||||
|
public float MachineGunAimDelay { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("machineGunBurstCount")]
|
||||||
|
public BtrMinMaxValue MachineGunBurstCount { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("machineGunRecoveryTime")]
|
||||||
|
public BtrMinMaxValue MachineGunRecoveryTime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BtrMinMaxValue
|
||||||
|
{
|
||||||
|
[JsonProperty("min")]
|
||||||
|
public float Min { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("max")]
|
||||||
|
public float Max { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.UI.Screens;
|
||||||
|
using EFT.Vehicle;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using BTRDialog = EFT.UI.TraderDialogScreen.GClass3132;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
public class BTRActivateTraderDialogPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static FieldInfo _playerInventoryControllerField;
|
||||||
|
private static FieldInfo _playerQuestControllerField;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
_playerInventoryControllerField = AccessTools.Field(typeof(Player), "_inventoryController");
|
||||||
|
_playerQuestControllerField = AccessTools.Field(typeof(Player), "_questController");
|
||||||
|
|
||||||
|
var targetType = AccessTools.FirstInner(typeof(GetActionsClass), IsTargetType);
|
||||||
|
return AccessTools.Method(targetType, "method_2");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsTargetType(Type type)
|
||||||
|
{
|
||||||
|
FieldInfo btrField = type.GetField("btr");
|
||||||
|
|
||||||
|
if (btrField != null && btrField.FieldType == typeof(BTRSide))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix()
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
var player = gameWorld.MainPlayer;
|
||||||
|
|
||||||
|
InventoryControllerClass inventoryController = _playerInventoryControllerField.GetValue(player) as InventoryControllerClass;
|
||||||
|
AbstractQuestControllerClass questController = _playerQuestControllerField.GetValue(player) as AbstractQuestControllerClass;
|
||||||
|
|
||||||
|
BTRDialog btrDialog = new BTRDialog(player.Profile, Profile.TraderInfo.TraderServiceToId[Profile.ETraderServiceSource.Btr], questController, inventoryController, null);
|
||||||
|
btrDialog.OnClose += player.UpdateInteractionCast;
|
||||||
|
btrDialog.ShowScreen(EScreenState.Queued);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
167
project/Aki.Custom/BTR/Patches/BTRBotAttachPatch.cs
Normal file
167
project/Aki.Custom/BTR/Patches/BTRBotAttachPatch.cs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.AssetsManager;
|
||||||
|
using EFT.NextObservedPlayer;
|
||||||
|
using EFT.Vehicle;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
// Fixes the BTR Bot initialization in AttachBot() of BTRTurretView
|
||||||
|
//
|
||||||
|
// Context:
|
||||||
|
// ClientGameWorld in LiveEFT will register the server-side BTR Bot as type ObservedPlayerView and is stored in GameWorld's allObservedPlayersByID dictionary.
|
||||||
|
// In SPT, GameWorld.allObservedPlayersByID is empty which results in the game never finishing the initialization of the BTR Bot which includes disabling its gun, voice and mesh renderers.
|
||||||
|
//
|
||||||
|
// This is essentially a full reimplementation of the BTRTurretView class, but using Player instead of ObservedPlayerView.
|
||||||
|
//
|
||||||
|
public class BTRBotAttachPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static FieldInfo _valueTuple0Field;
|
||||||
|
private static FieldInfo _gunModsToDisableField;
|
||||||
|
private static FieldInfo _weaponPrefab0Field;
|
||||||
|
private static readonly List<Renderer> rendererList = new List<Renderer>(256);
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var targetType = typeof(BTRTurretView);
|
||||||
|
|
||||||
|
_valueTuple0Field = AccessTools.Field(targetType, "valueTuple_0");
|
||||||
|
_gunModsToDisableField = AccessTools.Field(targetType, "_gunModsToDisable");
|
||||||
|
_weaponPrefab0Field = AccessTools.Field(typeof(Player.FirearmController), "weaponPrefab_0");
|
||||||
|
|
||||||
|
return AccessTools.Method(targetType, nameof(BTRTurretView.AttachBot));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(BTRTurretView __instance, int btrBotId)
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
if (gameWorld == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the BTR turret
|
||||||
|
var alivePlayersList = gameWorld.AllAlivePlayersList;
|
||||||
|
Player turretPlayer = alivePlayersList.FirstOrDefault(x => x.Id == btrBotId);
|
||||||
|
if (turretPlayer == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init the turret view
|
||||||
|
var valueTuple = (ValueTuple<ObservedPlayerView, bool>)_valueTuple0Field.GetValue(__instance);
|
||||||
|
if (!valueTuple.Item2 && !InitTurretView(__instance, turretPlayer))
|
||||||
|
{
|
||||||
|
Logger.LogError("[AKI-BTR] BTRBotAttachPatch - BtrBot initialization failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WeaponPrefab weaponPrefab;
|
||||||
|
Transform transform;
|
||||||
|
if (FindTurretObjects(turretPlayer, out weaponPrefab, out transform))
|
||||||
|
{
|
||||||
|
weaponPrefab.transform.SetPositionAndRotation(__instance.GunRoot.position, __instance.GunRoot.rotation);
|
||||||
|
transform.SetPositionAndRotation(__instance.GunRoot.position, __instance.GunRoot.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool InitTurretView(BTRTurretView btrTurretView, Player turretPlayer)
|
||||||
|
{
|
||||||
|
EnableTurretObjects(btrTurretView, turretPlayer, false);
|
||||||
|
|
||||||
|
// We only use this for tracking whether the turret is initialized, so we don't need to set the ObservedPlayerView
|
||||||
|
_valueTuple0Field.SetValue(btrTurretView, new ValueTuple<ObservedPlayerView, bool>(null, true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnableTurretObjects(BTRTurretView btrTurretView, Player player, bool enable)
|
||||||
|
{
|
||||||
|
// Find the turret weapon transform
|
||||||
|
WeaponPrefab weaponPrefab;
|
||||||
|
Transform weaponTransform;
|
||||||
|
if (!FindTurretObjects(player, out weaponPrefab, out weaponTransform))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the turret bot
|
||||||
|
SetVisible(player, weaponPrefab, false);
|
||||||
|
|
||||||
|
// Disable the components we need to disaable
|
||||||
|
var _gunModsToDisable = (string[])_gunModsToDisableField.GetValue(btrTurretView);
|
||||||
|
foreach (Transform child in weaponTransform)
|
||||||
|
{
|
||||||
|
if (_gunModsToDisable.Contains(child.name))
|
||||||
|
{
|
||||||
|
child.gameObject.SetActive(enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool FindTurretObjects(Player player, out WeaponPrefab weaponPrefab, out Transform weapon)
|
||||||
|
{
|
||||||
|
// Find the WeaponPrefab and Transform of the turret weapon
|
||||||
|
var aiFirearmController = player.gameObject.GetComponent<Player.FirearmController>();
|
||||||
|
weaponPrefab = (WeaponPrefab)_weaponPrefab0Field.GetValue(aiFirearmController);
|
||||||
|
|
||||||
|
if (weaponPrefab == null)
|
||||||
|
{
|
||||||
|
weapon = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
weapon = weaponPrefab.Hierarchy.GetTransform(ECharacterWeaponBones.weapon);
|
||||||
|
return weapon != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A re-implementation of the ObservedPlayerController.Culling.Mode setter that works for a Player object
|
||||||
|
*/
|
||||||
|
private static void SetVisible(Player player, WeaponPrefab weaponPrefab, bool isVisible)
|
||||||
|
{
|
||||||
|
// Toggle any animators and colliders
|
||||||
|
if (player.HealthController.IsAlive)
|
||||||
|
{
|
||||||
|
IAnimator bodyAnimatorCommon = player.GetBodyAnimatorCommon();
|
||||||
|
if (bodyAnimatorCommon.enabled != isVisible)
|
||||||
|
{
|
||||||
|
bool flag = !bodyAnimatorCommon.enabled;
|
||||||
|
bodyAnimatorCommon.enabled = isVisible;
|
||||||
|
FirearmsAnimator firearmsAnimator = player.HandsController.FirearmsAnimator;
|
||||||
|
if (firearmsAnimator != null && firearmsAnimator.Animator.enabled != isVisible)
|
||||||
|
{
|
||||||
|
firearmsAnimator.Animator.enabled = isVisible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerPoolObject component = player.gameObject.GetComponent<PlayerPoolObject>();
|
||||||
|
foreach (Collider collider in component.Colliders)
|
||||||
|
{
|
||||||
|
if (collider.enabled != isVisible)
|
||||||
|
{
|
||||||
|
collider.enabled = isVisible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a list of renderers for this player object and set their rendering state
|
||||||
|
rendererList.Clear();
|
||||||
|
player.PlayerBody.GetRenderersNonAlloc(rendererList);
|
||||||
|
if (weaponPrefab != null)
|
||||||
|
{
|
||||||
|
rendererList.AddRange(weaponPrefab.Renderers);
|
||||||
|
}
|
||||||
|
rendererList.ForEach(renderer => renderer.forceRenderingOff = !isVisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
project/Aki.Custom/BTR/Patches/BTRDestroyAtRaidEndPatch.cs
Normal file
34
project/Aki.Custom/BTR/Patches/BTRDestroyAtRaidEndPatch.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
public class BTRDestroyAtRaidEndPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Stop));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix()
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
if (gameWorld == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var btrManager = gameWorld.GetComponent<BTRManager>();
|
||||||
|
if (btrManager != null)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("[AKI-BTR] BTRDestroyAtRaidEndPatch - Raid Ended: Destroying BTRManager");
|
||||||
|
Object.Destroy(btrManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Custom.BTR.Utils;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
public class BTREndRaidItemDeliveryPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static JsonConverter[] _defaultJsonConverters;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
var converterClass = typeof(AbstractGame).Assembly.GetTypes()
|
||||||
|
.First(t => t.GetField("Converters", BindingFlags.Static | BindingFlags.Public) != null);
|
||||||
|
_defaultJsonConverters = Traverse.Create(converterClass).Field<JsonConverter[]>("Converters").Value;
|
||||||
|
|
||||||
|
Type baseLocalGameType = PatchConstants.LocalGameType.BaseType;
|
||||||
|
return AccessTools.Method(baseLocalGameType, nameof(LocalGame.Stop));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static void PatchPrefix()
|
||||||
|
{
|
||||||
|
GameWorld gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
if (gameWorld == null)
|
||||||
|
{
|
||||||
|
Logger.LogError("[AKI-BTR] BTREndRaidItemDeliveryPatch - GameWorld is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var player = gameWorld.MainPlayer;
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
Logger.LogError("[AKI-BTR] BTREndRaidItemDeliveryPatch - Player is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match doesn't have a BTR
|
||||||
|
if (gameWorld.BtrController == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gameWorld.BtrController.HasNonEmptyTransferContainer(player.Profile.Id))
|
||||||
|
{
|
||||||
|
Logger.LogDebug("[AKI-BTR] BTREndRaidItemDeliveryPatch - No items in transfer container");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var btrStash = gameWorld.BtrController.GetOrAddTransferContainer(player.Profile.Id);
|
||||||
|
var flatItems = Singleton<ItemFactory>.Instance.TreeToFlatItems(btrStash.Grid.Items);
|
||||||
|
|
||||||
|
RequestHandler.PutJson("/singleplayer/traderServices/itemDelivery", new
|
||||||
|
{
|
||||||
|
items = flatItems,
|
||||||
|
traderId = BTRUtil.BTRTraderId
|
||||||
|
}.ToJson(_defaultJsonConverters));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
project/Aki.Custom/BTR/Patches/BTRExtractPassengersPatch.cs
Normal file
48
project/Aki.Custom/BTR/Patches/BTRExtractPassengersPatch.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.Vehicle;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
public class BTRExtractPassengersPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(VehicleBase), nameof(VehicleBase.ExtractPassengers));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix()
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
var player = gameWorld.MainPlayer;
|
||||||
|
var btrManager = gameWorld.GetComponent<BTRManager>();
|
||||||
|
|
||||||
|
var btrSide = btrManager.LastInteractedBtrSide;
|
||||||
|
if (btrSide == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btrSide.TryGetCachedPlace(out byte b))
|
||||||
|
{
|
||||||
|
var interactionBtrPacket = btrSide.GetInteractWithBtrPacket(b, EInteractionType.GoOut);
|
||||||
|
if (interactionBtrPacket.HasInteraction)
|
||||||
|
{
|
||||||
|
BTRView btrView = gameWorld.BtrController.BtrView;
|
||||||
|
if (btrView == null)
|
||||||
|
{
|
||||||
|
Logger.LogError($"[AKI-BTR] BTRExtractPassengersPatch - btrView is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btrView.Interaction(player, interactionBtrPacket);
|
||||||
|
btrManager.OnPlayerInteractDoor(interactionBtrPacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
project/Aki.Custom/BTR/Patches/BTRInteractionPatch.cs
Normal file
58
project/Aki.Custom/BTR/Patches/BTRInteractionPatch.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.GlobalEvents;
|
||||||
|
using EFT.Vehicle;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
public class BTRInteractionPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.FirstMethod(typeof(Player), IsTargetMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the "BtrInteraction" method that takes parameters
|
||||||
|
*/
|
||||||
|
private bool IsTargetMethod(MethodBase method)
|
||||||
|
{
|
||||||
|
return method.Name == nameof(Player.BtrInteraction) && method.GetParameters().Length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(Player __instance, BTRSide btr, byte placeId, EInteractionType interaction)
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
var btrManager = gameWorld.GetComponent<BTRManager>();
|
||||||
|
|
||||||
|
var interactionBtrPacket = btr.GetInteractWithBtrPacket(placeId, interaction);
|
||||||
|
__instance.UpdateInteractionCast();
|
||||||
|
|
||||||
|
// Prevent player from entering BTR when blacklisted
|
||||||
|
var btrBot = gameWorld.BtrController.BotShooterBtr;
|
||||||
|
if (btrBot.BotsGroup.Enemies.ContainsKey(__instance))
|
||||||
|
{
|
||||||
|
// Notify player they are blacklisted from entering BTR
|
||||||
|
GlobalEventHandlerClass.CreateEvent<BtrNotificationInteractionMessageEvent>().Invoke(__instance.Id, EBtrInteractionStatus.Blacklisted);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interactionBtrPacket.HasInteraction)
|
||||||
|
{
|
||||||
|
BTRView btrView = gameWorld.BtrController.BtrView;
|
||||||
|
if (btrView == null)
|
||||||
|
{
|
||||||
|
Logger.LogError("[AKI-BTR] BTRInteractionPatch - btrView is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btrView.Interaction(__instance, interactionBtrPacket);
|
||||||
|
btrManager.OnPlayerInteractDoor(interactionBtrPacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
project/Aki.Custom/BTR/Patches/BTRIsDoorsClosedPatch.cs
Normal file
42
project/Aki.Custom/BTR/Patches/BTRIsDoorsClosedPatch.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
public class BTRIsDoorsClosedPath : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(VehicleBase), nameof(VehicleBase.IsDoorsClosed));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref bool __result)
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
if (gameWorld == null)
|
||||||
|
{
|
||||||
|
Logger.LogError("[AKI-BTR] BTRIsDoorsClosedPatch - GameWorld is null");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverSideBTR = gameWorld.BtrController.BtrVehicle;
|
||||||
|
if (serverSideBTR == null)
|
||||||
|
{
|
||||||
|
Logger.LogError("[AKI-BTR] BTRIsDoorsClosedPatch - serverSideBTR is null");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverSideBTR.LeftSideState == 0 && serverSideBTR.RightSideState == 0)
|
||||||
|
{
|
||||||
|
__result = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
project/Aki.Custom/BTR/Patches/BTRPatch.cs
Normal file
47
project/Aki.Custom/BTR/Patches/BTRPatch.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.UI;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a BTRManager component to the GameWorld game object when raid starts.
|
||||||
|
/// </summary>
|
||||||
|
public class BTRPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
// Note: This may seem like a weird place to hook, but `SetTime` requires that the BtrController
|
||||||
|
// exist and be setup, so we'll use this as the entry point
|
||||||
|
return AccessTools.Method(typeof(ExtractionTimersPanel), nameof(ExtractionTimersPanel.SetTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var btrSettings = Singleton<BackendConfigSettingsClass>.Instance.BTRSettings;
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
|
||||||
|
// Only run on maps that have the BTR enabled
|
||||||
|
string location = gameWorld.MainPlayer.Location;
|
||||||
|
if (!btrSettings.LocationsWithBTR.Contains(location))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameWorld.gameObject.AddComponent<BTRManager>();
|
||||||
|
}
|
||||||
|
catch (System.Exception)
|
||||||
|
{
|
||||||
|
ConsoleScreen.LogError("[AKI-BTR] Exception thrown, check logs.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
project/Aki.Custom/BTR/Patches/BTRPathLoadPatch.cs
Normal file
35
project/Aki.Custom/BTR/Patches/BTRPathLoadPatch.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
// The BTRManager MapPathsConfiguration loading depends on the game state being set to Starting
|
||||||
|
// so set it to Starting while the method is running, then reset it afterwards
|
||||||
|
public class BTRPathLoadPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static PropertyInfo _statusProperty;
|
||||||
|
private static GameStatus originalStatus;
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
_statusProperty = AccessTools.Property(typeof(AbstractGame), nameof(AbstractGame.Status));
|
||||||
|
|
||||||
|
return AccessTools.Method(typeof(BTRControllerClass), nameof(BTRControllerClass.method_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix()
|
||||||
|
{
|
||||||
|
originalStatus = Singleton<AbstractGame>.Instance.Status;
|
||||||
|
_statusProperty.SetValue(Singleton<AbstractGame>.Instance, GameStatus.Starting);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix()
|
||||||
|
{
|
||||||
|
_statusProperty.SetValue(Singleton<AbstractGame>.Instance, originalStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
project/Aki.Custom/BTR/Patches/BTRReceiveDamageInfoPatch.cs
Normal file
34
project/Aki.Custom/BTR/Patches/BTRReceiveDamageInfoPatch.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.Vehicle;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Patches an empty method in <see cref="BTRView"/> to handle updating the BTR bot's Neutrals and Enemies lists in response to taking damage.
|
||||||
|
/// </summary>
|
||||||
|
public class BTRReceiveDamageInfoPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BTRView), nameof(BTRView.method_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(DamageInfo damageInfo)
|
||||||
|
{
|
||||||
|
var botEventHandler = Singleton<BotEventHandler>.Instance;
|
||||||
|
if (botEventHandler == null)
|
||||||
|
{
|
||||||
|
Logger.LogError($"[AKI-BTR] BTRReceiveDamageInfoPatch - BotEventHandler is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shotBy = (Player)damageInfo.Player.iPlayer;
|
||||||
|
botEventHandler.InterruptTraderServiceBtrSupportByBetrayer(shotBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
project/Aki.Custom/BTR/Patches/BTRTransferItemsPatch.cs
Normal file
31
project/Aki.Custom/BTR/Patches/BTRTransferItemsPatch.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Aki.Custom.BTR.Utils;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.SinglePlayer.Utils.TraderServices;
|
||||||
|
using EFT.UI;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
public class BTRTransferItemsPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
|
||||||
|
return AccessTools.Method(typeof(TransferItemsInRaidScreen), nameof(TransferItemsInRaidScreen.Close));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(bool ___bool_1)
|
||||||
|
{
|
||||||
|
// Didn't extract items
|
||||||
|
if (!___bool_1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the trader services information now that we've used this service
|
||||||
|
TraderServicesManager.Instance.GetTraderServicesDataFromServer(BTRUtil.BTRTraderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
project/Aki.Custom/BTR/Patches/BTRTurretCanShootPatch.cs
Normal file
29
project/Aki.Custom/BTR/Patches/BTRTurretCanShootPatch.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT.Vehicle;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
public class BTRTurretCanShootPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BTRTurretServer), nameof(BTRTurretServer.method_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(BTRTurretServer __instance, Transform ___defaultTargetTransform)
|
||||||
|
{
|
||||||
|
bool flag = __instance.targetTransform != null && __instance.targetTransform != ___defaultTargetTransform;
|
||||||
|
bool flag2 = __instance.method_2();
|
||||||
|
bool flag3 = __instance.targetPosition != __instance.defaultAimingPosition;
|
||||||
|
|
||||||
|
var isCanShootProperty = AccessTools.DeclaredProperty(__instance.GetType(), nameof(__instance.IsCanShoot));
|
||||||
|
isCanShootProperty.SetValue(__instance, (flag || flag3) && flag2);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT.Vehicle;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
public class BTRTurretDefaultAimingPositionPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BTRTurretServer), nameof(BTRTurretServer.Start));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(BTRTurretServer __instance)
|
||||||
|
{
|
||||||
|
__instance.defaultAimingPosition = Vector3.zero;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT.Vehicle;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Patches
|
||||||
|
{
|
||||||
|
public class BTRVehicleMovementSpeedPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BTRVehicle), nameof(BTRVehicle.Update));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(ref float ___float_10, float ___moveSpeed)
|
||||||
|
{
|
||||||
|
___float_10 = ___moveSpeed * Time.deltaTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
project/Aki.Custom/BTR/Utils/BTRUtil.cs
Normal file
32
project/Aki.Custom/BTR/Utils/BTRUtil.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Custom.BTR.Models;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Aki.Custom.BTR.Utils
|
||||||
|
{
|
||||||
|
public static class BTRUtil
|
||||||
|
{
|
||||||
|
public static readonly string BTRTraderId = Profile.TraderInfo.BTR_TRADER_ID;
|
||||||
|
public static readonly string BTRMachineGunWeaponTplId = "657857faeff4c850222dff1b"; // BTR PKTM machine gun
|
||||||
|
public static readonly string BTRMachineGunAmmoTplId = "5e023d34e8a400319a28ed44"; // 7.62x54mmR BT
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to create an instance of the item in-raid.
|
||||||
|
/// </summary>
|
||||||
|
public static Item CreateItem(string tplId)
|
||||||
|
{
|
||||||
|
var id = Guid.NewGuid().ToString("N").Substring(0, 24);
|
||||||
|
return Singleton<ItemFactory>.Instance.CreateItem(id, tplId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BTRConfigModel GetConfigFromServer()
|
||||||
|
{
|
||||||
|
string json = RequestHandler.GetJson("/singleplayer/btr/config");
|
||||||
|
return JsonConvert.DeserializeObject<BTRConfigModel>(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,9 +22,9 @@ namespace Aki.Custom.CustomAI
|
|||||||
return (int)botRoleToCheck == AkiBotsPrePatcher.sptBearValue || (int)botRoleToCheck == AkiBotsPrePatcher.sptUsecValue;
|
return (int)botRoleToCheck == AkiBotsPrePatcher.sptBearValue || (int)botRoleToCheck == AkiBotsPrePatcher.sptUsecValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool BotIsPlayerScav(WildSpawnType role, BotOwner ___botOwner_0)
|
public static bool BotIsPlayerScav(WildSpawnType role, string nickname)
|
||||||
{
|
{
|
||||||
if (___botOwner_0.Profile.Info.Nickname.Contains("(") && role == WildSpawnType.assault)
|
if (role == WildSpawnType.assault && nickname.Contains("("))
|
||||||
{
|
{
|
||||||
// Check bot is pscav by looking for the opening parentheses of their nickname e.g. scavname (pmc name)
|
// Check bot is pscav by looking for the opening parentheses of their nickname e.g. scavname (pmc name)
|
||||||
return true;
|
return true;
|
||||||
|
@ -16,7 +16,9 @@ namespace Aki.Custom.CustomAI
|
|||||||
private static readonly string throwableItemId = "543be6564bdc2df4348b4568";
|
private static readonly string throwableItemId = "543be6564bdc2df4348b4568";
|
||||||
private static readonly string ammoItemId = "5485a8684bdc2da71d8b4567";
|
private static readonly string ammoItemId = "5485a8684bdc2da71d8b4567";
|
||||||
private static readonly string weaponId = "5422acb9af1c889c16000029";
|
private static readonly string weaponId = "5422acb9af1c889c16000029";
|
||||||
private static readonly List<string> nonFiRItems = new List<string>() { magazineId, drugId, mediKitItem, medicalItemId, injectorItemId, throwableItemId, ammoItemId };
|
private static readonly string armorPlate = "644120aa86ffbe10ee032b6f";
|
||||||
|
private static readonly string builtInInserts = "65649eb40bf0ed77b8044453";
|
||||||
|
private static readonly List<string> nonFiRItems = new List<string>() { magazineId, drugId, mediKitItem, medicalItemId, injectorItemId, throwableItemId, ammoItemId, armorPlate, builtInInserts };
|
||||||
|
|
||||||
private static readonly string pistolId = "5447b5cf4bdc2d65278b4567";
|
private static readonly string pistolId = "5447b5cf4bdc2d65278b4567";
|
||||||
private static readonly string smgId = "5447b5e04bdc2d62278b4567";
|
private static readonly string smgId = "5447b5e04bdc2d62278b4567";
|
||||||
@ -30,6 +32,7 @@ namespace Aki.Custom.CustomAI
|
|||||||
private static readonly string knifeId = "5447e1d04bdc2dff2f8b4567";
|
private static readonly string knifeId = "5447e1d04bdc2dff2f8b4567";
|
||||||
|
|
||||||
private static readonly List<string> weaponTypeIds = new List<string>() { pistolId, smgId, assaultRifleId, assaultCarbineId, shotgunId, marksmanRifleId, sniperRifleId, machinegunId, grenadeLauncherId, knifeId };
|
private static readonly List<string> weaponTypeIds = new List<string>() { pistolId, smgId, assaultRifleId, assaultCarbineId, shotgunId, marksmanRifleId, sniperRifleId, machinegunId, grenadeLauncherId, knifeId };
|
||||||
|
private static readonly List<string> nonFiRPocketLoot = new List<string>{ throwableItemId, ammoItemId, magazineId, medicalItemId, mediKitItem, injectorItemId, drugId };
|
||||||
private readonly ManualLogSource logger;
|
private readonly ManualLogSource logger;
|
||||||
|
|
||||||
public PmcFoundInRaidEquipment(ManualLogSource logger)
|
public PmcFoundInRaidEquipment(ManualLogSource logger)
|
||||||
@ -42,20 +45,34 @@ namespace Aki.Custom.CustomAI
|
|||||||
// Must run before the container loot code, otherwise backpack loot is not FiR
|
// Must run before the container loot code, otherwise backpack loot is not FiR
|
||||||
MakeEquipmentNotFiR(___botOwner_0);
|
MakeEquipmentNotFiR(___botOwner_0);
|
||||||
|
|
||||||
// Get inventory items that hold other items (backpack/rig/pockets)
|
// Get inventory items that hold other items (backpack/rig/pockets/armor)
|
||||||
List<Slot> containerGear = ___botOwner_0.Profile.Inventory.Equipment.GetContainerSlots();
|
IReadOnlyList<Slot> containerGear = ___botOwner_0.Profile.Inventory.Equipment.ContainerSlots;
|
||||||
|
var nonFiRRootItems = new List<Item>();
|
||||||
foreach (var container in containerGear)
|
foreach (var container in containerGear)
|
||||||
{
|
{
|
||||||
|
var firstItem = container.Items.FirstOrDefault();
|
||||||
foreach (var item in container.ContainedItem.GetAllItems())
|
foreach (var item in container.ContainedItem.GetAllItems())
|
||||||
{
|
{
|
||||||
// Skip items that match container (array has itself as an item)
|
// Skip items that match container (array has itself as an item)
|
||||||
if (item.Id == container.Items.FirstOrDefault().Id)
|
if (item.Id == firstItem?.Id)
|
||||||
{
|
{
|
||||||
//this.logger.LogError($"Skipping item {item.Id} {item.Name} as its same as container {container.FullId}");
|
//this.logger.LogError($"Skipping item {item.Id} {item.Name} as its same as container {container.FullId}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dont add FiR to tacvest items PMC usually brings into raid (meds/mags etc)
|
// 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))
|
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");
|
//this.logger.LogError($"Skipping item {item.Id} {item.Name} as its on the item type blacklist");
|
||||||
@ -65,25 +82,37 @@ namespace Aki.Custom.CustomAI
|
|||||||
// Don't add FiR to weapons in backpack (server sometimes adds pre-made weapons to backpack to simulate PMCs looting bodies)
|
// 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))
|
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");
|
//this.logger.LogError($"Skipping item {item.Id} {item.Name} as its on the item type blacklist");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't add FiR to grenades/mags/ammo in pockets
|
// Don't add FiR to grenades/mags/ammo/meds in pockets
|
||||||
if (container.Name == "Pockets" && new List<string> { throwableItemId, ammoItemId, magazineId, medicalItemId }.Any(item.Template._parent.Contains))
|
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");
|
//this.logger.LogError($"Skipping item {item.Id} {item.Name} as its on the item type blacklist");
|
||||||
continue;
|
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}");
|
//Logger.LogError($"flagging item FiR: {item.Id} {item.Name} _parent: {item.Template._parent}");
|
||||||
item.SpawnedInSession = true;
|
item.SpawnedInSession = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set dogtag as FiR
|
// Set dogtag as FiR
|
||||||
var dogtag = ___botOwner_0.Profile.Inventory.GetItemsInSlots(new EquipmentSlot[] { EquipmentSlot.Dogtag });
|
var dogtag = ___botOwner_0.Profile.Inventory.GetItemsInSlots(new EquipmentSlot[] { EquipmentSlot.Dogtag }).FirstOrDefault();
|
||||||
dogtag.FirstOrDefault().SpawnedInSession = true;
|
if (dogtag != null)
|
||||||
|
{
|
||||||
|
dogtag.SpawnedInSession = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
5
project/Aki.Custom/Models/BsgLoggingResponse.cs
Normal file
5
project/Aki.Custom/Models/BsgLoggingResponse.cs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
public struct LoggingLevelResponse
|
||||||
|
{
|
||||||
|
public int verbosity { get; set; }
|
||||||
|
public bool sendToServer {get; set; }
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
namespace Aki.Custom.Models
|
#region DEPRECATED, REMOVE IN 3.8.1
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Models
|
||||||
{
|
{
|
||||||
|
[Obsolete("BundleInfo is deprecated, please use BundleItem instead.")]
|
||||||
public class BundleInfo
|
public class BundleInfo
|
||||||
{
|
{
|
||||||
public string Key { get; }
|
public string Key { get; }
|
||||||
@ -14,3 +19,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
@ -1,9 +1,29 @@
|
|||||||
namespace Aki.Custom.Models
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Models
|
||||||
{
|
{
|
||||||
|
[DataContract]
|
||||||
public struct BundleItem
|
public struct BundleItem
|
||||||
{
|
{
|
||||||
|
[DataMember(Name = "filename")]
|
||||||
public string FileName;
|
public string FileName;
|
||||||
|
|
||||||
|
[DataMember(Name = "crc")]
|
||||||
public uint Crc;
|
public uint Crc;
|
||||||
|
|
||||||
|
[DataMember(Name = "dependencies")]
|
||||||
public string[] Dependencies;
|
public string[] Dependencies;
|
||||||
|
|
||||||
|
// exclusive to aki, ignored in EscapeFromTarkov_Data/StreamingAssets/Windows/Windows.json
|
||||||
|
[DataMember(Name = "modpath")]
|
||||||
|
public string ModPath;
|
||||||
|
|
||||||
|
public BundleItem(string filename, uint crc, string[] dependencies, string modpath = "")
|
||||||
|
{
|
||||||
|
FileName = filename;
|
||||||
|
Crc = crc;
|
||||||
|
Dependencies = dependencies;
|
||||||
|
ModPath = modpath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,10 @@ namespace Aki.Custom.Models
|
|||||||
public bool ScavWars;
|
public bool ScavWars;
|
||||||
public bool TaggedAndCursed;
|
public bool TaggedAndCursed;
|
||||||
public bool EnablePve;
|
public bool EnablePve;
|
||||||
|
public bool RandomWeather;
|
||||||
|
public bool RandomTime;
|
||||||
|
|
||||||
public DefaultRaidSettings(EBotAmount aiAmount, EBotDifficulty aiDifficulty, bool bossEnabled, bool scavWars, bool taggedAndCursed, bool enablePve)
|
public DefaultRaidSettings(EBotAmount aiAmount, EBotDifficulty aiDifficulty, bool bossEnabled, bool scavWars, bool taggedAndCursed, bool enablePve, bool randomWeather, bool randomTime)
|
||||||
{
|
{
|
||||||
AiAmount = aiAmount;
|
AiAmount = aiAmount;
|
||||||
AiDifficulty = aiDifficulty;
|
AiDifficulty = aiDifficulty;
|
||||||
@ -19,6 +21,8 @@ namespace Aki.Custom.Models
|
|||||||
ScavWars = scavWars;
|
ScavWars = scavWars;
|
||||||
TaggedAndCursed = taggedAndCursed;
|
TaggedAndCursed = taggedAndCursed;
|
||||||
EnablePve = enablePve;
|
EnablePve = enablePve;
|
||||||
|
RandomWeather = randomWeather;
|
||||||
|
RandomTime = randomTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
project/Aki.Custom/Models/ReleaseResponse.cs
Normal file
19
project/Aki.Custom/Models/ReleaseResponse.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace Aki.Custom.Models
|
||||||
|
{
|
||||||
|
public struct ReleaseResponse
|
||||||
|
{
|
||||||
|
public bool isBeta { get; set; }
|
||||||
|
public bool isModdable { get; set; }
|
||||||
|
public bool isModded { get; set; }
|
||||||
|
public float betaDisclaimerTimeoutDelay { get; set; }
|
||||||
|
public string betaDisclaimerText { get; set; }
|
||||||
|
public string betaDisclaimerAcceptText { get; set; }
|
||||||
|
public string serverModsLoadedText { get; set; }
|
||||||
|
public string serverModsLoadedDebugText { get; set; }
|
||||||
|
public string clientModsLoadedText { get; set; }
|
||||||
|
public string clientModsLoadedDebugText { get; set; }
|
||||||
|
public string illegalPluginsLoadedText { get; set; }
|
||||||
|
public string illegalPluginsExceptionText { get; set; }
|
||||||
|
public string releaseSummaryText { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,34 +0,0 @@
|
|||||||
using Aki.Reflection.Patching;
|
|
||||||
using EFT;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// If a bot being added has an ID found in list_1, it means its trying to add itself to its enemy list
|
|
||||||
/// Dont add bot to enemy list if its in list_1 and skip the rest of the AddEnemy() function
|
|
||||||
/// </summary>
|
|
||||||
public class AddSelfAsEnemyPatch : ModulePatch
|
|
||||||
{
|
|
||||||
private static readonly string methodName = "AddEnemy";
|
|
||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
|
||||||
{
|
|
||||||
return typeof(BotZoneGroupsDictionary).GetMethod(methodName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[PatchPrefix]
|
|
||||||
private static bool PatchPrefix(BotZoneGroupsDictionary __instance, IPlayer person)
|
|
||||||
{
|
|
||||||
var botOwners = (List<BotOwner>)__instance.GetType().GetField("list_1", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance);
|
|
||||||
if (botOwners.Any(x => x.Id == person.Id))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +1,15 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using EFT;
|
using EFT;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
public class AddEnemyToAllGroupsInBotZonePatch : ModulePatch
|
public class AddEnemyToAllGroupsInBotZonePatch : ModulePatch
|
||||||
{
|
{
|
||||||
private static Type _targetType;
|
|
||||||
private const string methodName = "AddEnemyToAllGroupsInBotZone";
|
|
||||||
|
|
||||||
public AddEnemyToAllGroupsInBotZonePatch()
|
|
||||||
{
|
|
||||||
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsTargetType(Type type)
|
|
||||||
{
|
|
||||||
if (type.Name == nameof(BotsController) && type.GetMethod(methodName) != null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"{this.GetType().Name} Type: {_targetType.Name}");
|
return AccessTools.Method(typeof(BotsController), nameof(BotsController.AddEnemyToAllGroupsInBotZone));
|
||||||
Logger.LogDebug($"{this.GetType().Name} Method: {methodName}");
|
|
||||||
|
|
||||||
return _targetType.GetMethod(methodName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -44,6 +21,17 @@ namespace Aki.Custom.Patches
|
|||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static bool PatchPrefix(BotsController __instance, IPlayer aggressor, IPlayer groupOwner, IPlayer target)
|
private static bool PatchPrefix(BotsController __instance, IPlayer aggressor, IPlayer groupOwner, IPlayer target)
|
||||||
{
|
{
|
||||||
|
if (!groupOwner.IsAI)
|
||||||
|
{
|
||||||
|
return false; // Skip original
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you damage yourself exit early as we dont want to try add ourself to our own enemy list
|
||||||
|
if (aggressor.IsYourPlayer && target.IsYourPlayer)
|
||||||
|
{
|
||||||
|
return false; // Skip original
|
||||||
|
}
|
||||||
|
|
||||||
BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone;
|
BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone;
|
||||||
foreach (var item in __instance.Groups())
|
foreach (var item in __instance.Groups())
|
||||||
{
|
{
|
||||||
@ -68,7 +56,7 @@ namespace Aki.Custom.Patches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false; // Skip original
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using System.Reflection;
|
|||||||
using Aki.PrePatch;
|
using Aki.PrePatch;
|
||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using EFT;
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
@ -10,7 +11,7 @@ namespace Aki.Custom.Patches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return typeof(BotSettingsRepoClass).GetMethod("Init");
|
return AccessTools.Method(typeof(BotSettingsRepoClass), nameof(BotSettingsRepoClass.Init));
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
|
64
project/Aki.Custom/Patches/AddTraitorScavsPatch.cs
Normal file
64
project/Aki.Custom/Patches/AddTraitorScavsPatch.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Custom.Airdrops.Models;
|
||||||
|
using Aki.Custom.CustomAI;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class AddTraitorScavsPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static int? TraitorChancePercent;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BotSpawner), nameof(BotSpawner.GetGroupAndSetEnemies));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref BotsGroup __result, IBotGame ____game, DeadBodiesController ____deadBodiesController, BotOwner bot, BotZone zone)
|
||||||
|
{
|
||||||
|
if (!TraitorChancePercent.HasValue)
|
||||||
|
{
|
||||||
|
string json = RequestHandler.GetJson("/singleplayer/scav/traitorscavhostile");
|
||||||
|
TraitorChancePercent = JsonConvert.DeserializeObject<int>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
WildSpawnType role = bot.Profile.Info.Settings.Role;
|
||||||
|
if (AiHelpers.BotIsPlayerScav(role, bot.Profile.Info.Nickname) && new Random().Next(1, 100) < TraitorChancePercent)
|
||||||
|
{
|
||||||
|
Logger.LogInfo($"Making {bot.name} ({bot.Profile.Nickname}) hostile to player");
|
||||||
|
|
||||||
|
// Create a new group for this scav itself to belong to
|
||||||
|
var player = Singleton<GameWorld>.Instance.MainPlayer;
|
||||||
|
var enemies = new List<BotOwner>();
|
||||||
|
var players = new List<Player>() { player };
|
||||||
|
var botsGroup = new BotsGroup(zone, ____game, bot, enemies, ____deadBodiesController, players, false);
|
||||||
|
|
||||||
|
// Because we don't want to use the zone-specific group, we add the new group with no key. This is similar to free for all
|
||||||
|
Singleton<IBotGame>.Instance.BotsController.BotSpawner.Groups.AddNoKey(botsGroup, zone);
|
||||||
|
botsGroup.AddEnemy(player, EBotEnemyCause.checkAddTODO);
|
||||||
|
|
||||||
|
// Make it so the player can kill the scav without aggroing the rest of the scavs
|
||||||
|
bot.Loyalty.CanBeFreeKilled = true;
|
||||||
|
|
||||||
|
// Traitors dont talk
|
||||||
|
bot.BotTalk.SetSilence(9999);
|
||||||
|
|
||||||
|
// Return our new botgroup
|
||||||
|
__result = botsGroup;
|
||||||
|
|
||||||
|
// Skip original
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
project/Aki.Custom/Patches/BetaLogoPatch.cs
Normal file
64
project/Aki.Custom/Patches/BetaLogoPatch.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT.UI;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
using Aki.SinglePlayer.Utils.MainMenu;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.MainMenu
|
||||||
|
{
|
||||||
|
public class BetaLogoPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(TarkovApplication), nameof(TarkovApplication.method_27));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(Profile profile)
|
||||||
|
{
|
||||||
|
MonoBehaviourSingleton<PreloaderUI>.Instance.SetWatermarkStatus(profile, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BetaLogoPatch2 : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(ClientWatermark), nameof(ClientWatermark.method_0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(ref TextMeshProUGUI ____label, Profile ___profile_0)
|
||||||
|
{
|
||||||
|
____label.text = $"{MenuNotificationManager.commitHash}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BetaLogoPatch3 : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(ClientWatermark), nameof(ClientWatermark.smethod_0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix so the logic isn't being duplicated.
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(int screenHeight, int screenWidth, int rectHeight, int rectWidth, ref Vector2 __result)
|
||||||
|
{
|
||||||
|
System.Random random = new System.Random();
|
||||||
|
|
||||||
|
int maxX = (screenWidth / 4) - (rectWidth / 2);
|
||||||
|
int maxY = (screenHeight / 4) - (rectHeight / 2);
|
||||||
|
int newX = random.Next(-maxX, maxX);
|
||||||
|
int newY = random.Next(-maxY, maxY);
|
||||||
|
|
||||||
|
__result = new Vector2(newX, newY);
|
||||||
|
|
||||||
|
// Skip original
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,11 +16,11 @@ namespace Aki.Custom.Patches
|
|||||||
{
|
{
|
||||||
var desiredType = PatchConstants.LocalGameType;
|
var desiredType = PatchConstants.LocalGameType;
|
||||||
var desiredMethod = desiredType
|
var desiredMethod = desiredType
|
||||||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)
|
.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
|
||||||
.SingleOrDefault(m => IsTargetMethod(m));
|
.SingleOrDefault(IsTargetMethod);
|
||||||
|
|
||||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType.Name}");
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType.Name}");
|
||||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod.Name}");
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name ?? "NOT FOUND"}");
|
||||||
|
|
||||||
return desiredMethod;
|
return desiredMethod;
|
||||||
}
|
}
|
||||||
|
54
project/Aki.Custom/Patches/BotCallForHelpCallBotPatch.cs
Normal file
54
project/Aki.Custom/Patches/BotCallForHelpCallBotPatch.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* BSG passes the wrong target location into the TryCall method for BotCallForHelp, it's passing in
|
||||||
|
* the bots current target instead of the target of the bot calling for help
|
||||||
|
*
|
||||||
|
* This results in both an NRE, and the called bots target location being wrong
|
||||||
|
*/
|
||||||
|
internal class BotCallForHelpCallBotPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static FieldInfo _originalPanicTypeField;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
_originalPanicTypeField = AccessTools.Field(typeof(BotCallForHelp), "_originalPanicType");
|
||||||
|
|
||||||
|
return AccessTools.FirstMethod(typeof(BotCallForHelp), IsTargetMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool IsTargetMethod(MethodBase method)
|
||||||
|
{
|
||||||
|
var parameters = method.GetParameters();
|
||||||
|
return (parameters.Length == 1
|
||||||
|
&& parameters[0].Name == "calledBot");
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref bool __result, BotCallForHelp __instance, BotOwner calledBot, BotOwner ___botOwner_0)
|
||||||
|
{
|
||||||
|
if (__instance.method_2(calledBot) && ___botOwner_0.Memory.GoalEnemy != null)
|
||||||
|
{
|
||||||
|
_originalPanicTypeField.SetValue(calledBot.CallForHelp, calledBot.DangerPointsData.PanicType);
|
||||||
|
calledBot.DangerPointsData.PanicType = PanicType.none;
|
||||||
|
calledBot.Brain.BaseBrain.CalcActionNextFrame();
|
||||||
|
// Note: This differs from BSG's implementation in that we pass in botOwner_0's enemy pos instead of calledBot's enemy pos
|
||||||
|
calledBot.CalledData.TryCall(new Vector3?(___botOwner_0.Memory.GoalEnemy.Person.Position), ___botOwner_0, true);
|
||||||
|
__result = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
__result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip original
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
project/Aki.Custom/Patches/BotCalledDataTryCallPatch.cs
Normal file
49
project/Aki.Custom/Patches/BotCalledDataTryCallPatch.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* It's possible for `AddEnemy` to return false, in that case, further code in TryCall will fail,
|
||||||
|
* so we do the first bit of `TryCall` ourselves, and skip the original function if AddEnemy fails
|
||||||
|
*/
|
||||||
|
internal class BotCalledDataTryCallPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BotCalledData), nameof(BotCalledData.TryCall));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static bool PatchPrefix(ref bool __result, BotOwner caller, BotOwner ___botOwner_0, BotOwner ____caller)
|
||||||
|
{
|
||||||
|
if (___botOwner_0.EnemiesController.IsEnemy(caller.AIData.Player) || ____caller != null)
|
||||||
|
{
|
||||||
|
__result = false;
|
||||||
|
|
||||||
|
// Skip original
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (caller.Memory.GoalEnemy != null)
|
||||||
|
{
|
||||||
|
IPlayer person = caller.Memory.GoalEnemy.Person;
|
||||||
|
if (!___botOwner_0.BotsGroup.Enemies.ContainsKey(person))
|
||||||
|
{
|
||||||
|
if (!___botOwner_0.BotsGroup.AddEnemy(person, EBotEnemyCause.callBot))
|
||||||
|
{
|
||||||
|
__result = false;
|
||||||
|
|
||||||
|
// Skip original
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow original
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ using Aki.Reflection.Patching;
|
|||||||
using Aki.Reflection.Utils;
|
using Aki.Reflection.Utils;
|
||||||
using EFT;
|
using EFT;
|
||||||
using EFT.UI;
|
using EFT.UI;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
@ -15,7 +14,7 @@ namespace Aki.Custom.Patches
|
|||||||
var methodName = "LoadDifficultyStringInternal";
|
var methodName = "LoadDifficultyStringInternal";
|
||||||
var flags = BindingFlags.Public | BindingFlags.Static;
|
var flags = BindingFlags.Public | BindingFlags.Static;
|
||||||
|
|
||||||
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
|
return PatchConstants.EftTypes.SingleCustom(x => x.GetMethod(methodName, flags) != null)
|
||||||
.GetMethod(methodName, flags);
|
.GetMethod(methodName, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,36 +1,15 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using EFT;
|
using EFT;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
public class BotEnemyTargetPatch : ModulePatch
|
public class BotEnemyTargetPatch : ModulePatch
|
||||||
{
|
{
|
||||||
private static Type _targetType;
|
|
||||||
private static readonly string methodName = "AddEnemyToAllGroupsInBotZone";
|
|
||||||
|
|
||||||
public BotEnemyTargetPatch()
|
|
||||||
{
|
|
||||||
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsTargetType(Type type)
|
|
||||||
{
|
|
||||||
if (type.Name == nameof(BotsController) && type.GetMethod(methodName) != null)
|
|
||||||
{
|
|
||||||
Logger.LogInfo($"{methodName}: {type.FullName}");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return _targetType.GetMethod(methodName);
|
return AccessTools.Method(typeof(BotsController), nameof(BotsController.AddEnemyToAllGroupsInBotZone));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
28
project/Aki.Custom/Patches/BotOwnerDisposePatch.cs
Normal file
28
project/Aki.Custom/Patches/BotOwnerDisposePatch.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* BotOwner doesn't call SetOff on the CalledData object when a bot is disposed, this can result
|
||||||
|
* in bots that are no longer alive having their `OnEnemyAdd` method called
|
||||||
|
*/
|
||||||
|
internal class BotOwnerDisposePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BotOwner), nameof(BotOwner.Dispose));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix(BotOwner __instance)
|
||||||
|
{
|
||||||
|
if (__instance.CalledData != null)
|
||||||
|
{
|
||||||
|
__instance.CalledData.SetOff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using EFT;
|
using EFT;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
@ -9,11 +10,9 @@ namespace Aki.Custom.Patches
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class BotSelfEnemyPatch : ModulePatch
|
internal class BotSelfEnemyPatch : ModulePatch
|
||||||
{
|
{
|
||||||
private static readonly string methodName = "PreActivate";
|
|
||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return typeof(BotOwner).GetMethod(methodName);
|
return AccessTools.Method(typeof(BotOwner), nameof(BotOwner.PreActivate));
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
|
@ -1,65 +1,29 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using EFT;
|
using EFT;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
public class CheckAndAddEnemyPatch : ModulePatch
|
public class CheckAndAddEnemyPatch : ModulePatch
|
||||||
{
|
{
|
||||||
private static Type _targetType;
|
|
||||||
private readonly string _targetMethodName = "CheckAndAddEnemy";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BotGroupClass.CheckAndAddEnemy()
|
|
||||||
/// </summary>
|
|
||||||
public CheckAndAddEnemyPatch()
|
|
||||||
{
|
|
||||||
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsTargetType(Type type)
|
|
||||||
{
|
|
||||||
if (type.GetMethod("AddEnemy") != null && type.GetMethod("AddEnemyGroupIfAllowed") != null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return _targetType.GetMethod(_targetMethodName);
|
return AccessTools.Method(typeof(BotsGroup), nameof(BotsGroup.CheckAndAddEnemy));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CheckAndAddEnemy()
|
/// CheckAndAddEnemy()
|
||||||
/// Goal: This patch lets bosses shoot back once a PMC has shot them
|
/// Goal: This patch lets bosses shoot back once a PMC has shot them
|
||||||
/// removes the !player.AIData.IsAI check
|
/// Removes the !player.AIData.IsAI check
|
||||||
|
/// BSG changed the way CheckAndAddEnemy Works in 14.0 Returns a bool now
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static bool PatchPrefix(BotsGroup __instance, IPlayer player, ref bool ignoreAI)
|
private static bool PatchPrefix(BotsGroup __instance, IPlayer player, ref bool __result)
|
||||||
{
|
{
|
||||||
// Z already has player as enemy BUT Enemies dict is empty, adding them again causes 'existing key' errors
|
// Set result to not include !player.AIData.IsAI checks
|
||||||
if (__instance.InitialBotType == WildSpawnType.bossZryachiy || __instance.InitialBotType == WildSpawnType.followerZryachiy)
|
__result = player.HealthController.IsAlive && !__instance.Enemies.ContainsKey(player) && __instance.AddEnemy(player, EBotEnemyCause.checkAddTODO);
|
||||||
{
|
return false; // Skip Original
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!player.HealthController.IsAlive)
|
|
||||||
{
|
|
||||||
return false; // Skip original
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!__instance.Enemies.ContainsKey(player))
|
|
||||||
{
|
|
||||||
__instance.AddEnemy(player, EBotEnemyCause.checkAddTODO);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // Skip original
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
project/Aki.Custom/Patches/ClampRagdollPatch.cs
Normal file
22
project/Aki.Custom/Patches/ClampRagdollPatch.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT.Interactive;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class ClampRagdollPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(Corpse), nameof(Corpse.method_16));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPreFix(ref Vector3 velocity)
|
||||||
|
{
|
||||||
|
velocity.y = Mathf.Clamp(velocity.y, -1f, 1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
using Aki.Reflection.Utils;
|
||||||
using Aki.Common.Http;
|
using Aki.Common.Http;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
@ -13,7 +12,7 @@ namespace Aki.Custom.Patches
|
|||||||
var methodName = "LoadCoreByString";
|
var methodName = "LoadCoreByString";
|
||||||
var flags = BindingFlags.Public | BindingFlags.Static;
|
var flags = BindingFlags.Public | BindingFlags.Static;
|
||||||
|
|
||||||
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
|
return PatchConstants.EftTypes.SingleCustom(x => x.GetMethod(methodName, flags) != null)
|
||||||
.GetMethod(methodName, flags);
|
.GetMethod(methodName, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
project/Aki.Custom/Patches/CultistAmuletRemovalPatch.cs
Normal file
36
project/Aki.Custom/Patches/CultistAmuletRemovalPatch.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using EFT;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
using EFT.UI.DragAndDrop;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class CultistAmuletRemovalPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(CultistEventsClass), nameof(CultistEventsClass.method_4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(DamageInfo damageInfo, Player victim)
|
||||||
|
{
|
||||||
|
var player = damageInfo.Player.iPlayer;
|
||||||
|
var amulet = player.FindCultistAmulet();
|
||||||
|
if (victim.Profile.Info.Settings.Role.IsSectant() && amulet != null)
|
||||||
|
{
|
||||||
|
var list = (player.Profile.Inventory.Equipment.GetSlot(EquipmentSlot.Pockets).ContainedItem as SearchableItemClass).Slots;
|
||||||
|
var amuletslot = list.Single(x => x.ContainedItem == amulet);
|
||||||
|
amuletslot.RemoveItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using Comfort.Common;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Aki.PrePatch;
|
using Aki.PrePatch;
|
||||||
using Aki.Custom.CustomAI;
|
using Aki.Custom.CustomAI;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
@ -15,7 +16,7 @@ namespace Aki.Custom.Patches
|
|||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return typeof(StandartBotBrain).GetMethod("Activate", BindingFlags.Public | BindingFlags.Instance);
|
return AccessTools.Method(typeof(StandartBotBrain), nameof(StandartBotBrain.Activate));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -28,12 +29,13 @@ namespace Aki.Custom.Patches
|
|||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static bool PatchPrefix(out WildSpawnType __state, StandartBotBrain __instance, BotOwner ___botOwner_0)
|
private static bool PatchPrefix(out WildSpawnType __state, StandartBotBrain __instance, BotOwner ___botOwner_0)
|
||||||
{
|
{
|
||||||
|
var player = Singleton<GameWorld>.Instance.MainPlayer;
|
||||||
___botOwner_0.Profile.Info.Settings.Role = FixAssaultGroupPmcsRole(___botOwner_0);
|
___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()
|
__state = ___botOwner_0.Profile.Info.Settings.Role; // Store original type in state param to allow access in PatchPostFix()
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string currentMapName = GetCurrentMap();
|
string currentMapName = GetCurrentMap();
|
||||||
if (AiHelpers.BotIsPlayerScav(__state, ___botOwner_0))
|
if (AiHelpers.BotIsPlayerScav(__state, ___botOwner_0.Profile.Info.Nickname))
|
||||||
{
|
{
|
||||||
___botOwner_0.Profile.Info.Settings.Role = aIBrainSpawnWeightAdjustment.GetRandomisedPlayerScavType(___botOwner_0, currentMapName);
|
___botOwner_0.Profile.Info.Settings.Role = aIBrainSpawnWeightAdjustment.GetRandomisedPlayerScavType(___botOwner_0, currentMapName);
|
||||||
|
|
||||||
@ -100,7 +102,7 @@ namespace Aki.Custom.Patches
|
|||||||
// Set spt bot bot back to original type
|
// Set spt bot bot back to original type
|
||||||
___botOwner_0.Profile.Info.Settings.Role = __state;
|
___botOwner_0.Profile.Info.Settings.Role = __state;
|
||||||
}
|
}
|
||||||
else if (AiHelpers.BotIsPlayerScav(__state, ___botOwner_0))
|
else if (AiHelpers.BotIsPlayerScav(__state, ___botOwner_0.Profile.Info.Nickname))
|
||||||
{
|
{
|
||||||
// Set pscav back to original type
|
// Set pscav back to original type
|
||||||
___botOwner_0.Profile.Info.Settings.Role = __state;
|
___botOwner_0.Profile.Info.Settings.Role = __state;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using Diz.Jobs;
|
using Diz.Jobs;
|
||||||
using Diz.Resources;
|
using Diz.Resources;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@ -12,27 +11,21 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Aki.Common.Utils;
|
||||||
using Aki.Custom.Models;
|
using Aki.Custom.Models;
|
||||||
using Aki.Custom.Utils;
|
using Aki.Custom.Utils;
|
||||||
using DependencyGraph = DependencyGraph<IEasyBundle>;
|
using DependencyGraph = DependencyGraph<IEasyBundle>;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
public class EasyAssetsPatch : ModulePatch
|
public class EasyAssetsPatch : ModulePatch
|
||||||
{
|
{
|
||||||
private static readonly FieldInfo _manifestField;
|
|
||||||
private static readonly FieldInfo _bundlesField;
|
private static readonly FieldInfo _bundlesField;
|
||||||
private static readonly PropertyInfo _systemProperty;
|
|
||||||
|
|
||||||
static EasyAssetsPatch()
|
static EasyAssetsPatch()
|
||||||
{
|
{
|
||||||
var type = typeof(EasyAssets);
|
_bundlesField = typeof(EasyAssets).GetField($"{EasyBundleHelper.Type.Name.ToLowerInvariant()}_0", PatchConstants.PrivateFlags);
|
||||||
|
|
||||||
_manifestField = type.GetField(nameof(EasyAssets.Manifest));
|
|
||||||
_bundlesField = type.GetField($"{EasyBundleHelper.Type.Name.ToLowerInvariant()}_0", PatchConstants.PrivateFlags);
|
|
||||||
|
|
||||||
// DependencyGraph<IEasyBundle>
|
|
||||||
_systemProperty = type.GetProperty("System");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public EasyAssetsPatch()
|
public EasyAssetsPatch()
|
||||||
@ -45,7 +38,7 @@ namespace Aki.Custom.Patches
|
|||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return typeof(EasyAssets).GetMethods(PatchConstants.PrivateFlags).Single(IsTargetMethod);
|
return typeof(EasyAssets).GetMethods(PatchConstants.PublicDeclaredFlags).SingleCustom(IsTargetMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsTargetMethod(MethodInfo mi)
|
private static bool IsTargetMethod(MethodInfo mi)
|
||||||
@ -65,42 +58,56 @@ namespace Aki.Custom.Patches
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task Init(EasyAssets instance, [CanBeNull] IBundleLock bundleLock, string defaultKey, string rootPath,
|
private static async Task Init(EasyAssets instance, [CanBeNull] IBundleLock bundleLock, string defaultKey, string rootPath, string platformName, [CanBeNull] Func<string, bool> shouldExclude, Func<string, Task> bundleCheck)
|
||||||
string platformName, [CanBeNull] Func<string, bool> shouldExclude, Func<string, Task> bundleCheck)
|
|
||||||
{
|
{
|
||||||
// platform manifest
|
// platform manifest
|
||||||
var path = $"{rootPath.Replace("file:///", string.Empty).Replace("file://", string.Empty)}/{platformName}/";
|
var path = $"{rootPath.Replace("file:///", string.Empty).Replace("file://", string.Empty)}/{platformName}/";
|
||||||
var filepath = path + platformName;
|
var filepath = path + platformName;
|
||||||
var manifest = (File.Exists(filepath)) ? await GetManifestBundle(filepath) : await GetManifestJson(filepath);
|
var jsonfile = filepath + ".json";
|
||||||
|
var manifest = File.Exists(jsonfile)
|
||||||
|
? await GetManifestJson(jsonfile)
|
||||||
|
: await GetManifestBundle(filepath);
|
||||||
|
|
||||||
// load bundles
|
// create bundles array from obfuscated type
|
||||||
var bundleNames = manifest.GetAllAssetBundles().Union(BundleManager.Bundles.Keys).ToArray();
|
var bundleNames = manifest.GetAllAssetBundles()
|
||||||
var bundles = (IEasyBundle[])Array.CreateInstance(EasyBundleHelper.Type, bundleNames.Length);
|
.Union(BundleManager.Bundles.Keys)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
// create bundle lock
|
||||||
if (bundleLock == null)
|
if (bundleLock == null)
|
||||||
{
|
{
|
||||||
bundleLock = new BundleLock(int.MaxValue);
|
bundleLock = new BundleLock(int.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create bundle of obfuscated type
|
||||||
|
var bundles = (IEasyBundle[])Array.CreateInstance(EasyBundleHelper.Type, bundleNames.Length);
|
||||||
|
|
||||||
for (var i = 0; i < bundleNames.Length; i++)
|
for (var i = 0; i < bundleNames.Length; i++)
|
||||||
{
|
{
|
||||||
bundles[i] = (IEasyBundle)Activator.CreateInstance(EasyBundleHelper.Type, new object[]
|
bundles[i] = (IEasyBundle)Activator.CreateInstance(EasyBundleHelper.Type, new object[]
|
||||||
{
|
{
|
||||||
bundleNames[i],
|
bundleNames[i],
|
||||||
path,
|
path,
|
||||||
manifest,
|
manifest,
|
||||||
bundleLock,
|
bundleLock,
|
||||||
bundleCheck
|
bundleCheck
|
||||||
});
|
});
|
||||||
|
|
||||||
await JobScheduler.Yield(EJobPriority.Immediate);
|
await JobScheduler.Yield(EJobPriority.Immediate);
|
||||||
}
|
}
|
||||||
|
|
||||||
_manifestField.SetValue(instance, manifest);
|
// create dependency graph
|
||||||
|
instance.Manifest = manifest;
|
||||||
_bundlesField.SetValue(instance, bundles);
|
_bundlesField.SetValue(instance, bundles);
|
||||||
_systemProperty.SetValue(instance, new DependencyGraph(bundles, defaultKey, shouldExclude));
|
instance.System = new DependencyGraph(bundles, defaultKey, shouldExclude);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: used by:
|
||||||
|
// - EscapeFromTarkov_Data/StreamingAssets/Windows/cubemaps
|
||||||
|
// - EscapeFromTarkov_Data/StreamingAssets/Windows/defaultmaterial
|
||||||
|
// - EscapeFromTarkov_Data/StreamingAssets/Windows/dissonancesetup
|
||||||
|
// - EscapeFromTarkov_Data/StreamingAssets/Windows/Doge
|
||||||
|
// - EscapeFromTarkov_Data/StreamingAssets/Windows/shaders
|
||||||
private static async Task<CompatibilityAssetBundleManifest> GetManifestBundle(string filepath)
|
private static async Task<CompatibilityAssetBundleManifest> GetManifestBundle(string filepath)
|
||||||
{
|
{
|
||||||
var manifestLoading = AssetBundle.LoadFromFileAsync(filepath);
|
var manifestLoading = AssetBundle.LoadFromFileAsync(filepath);
|
||||||
@ -115,16 +122,18 @@ namespace Aki.Custom.Patches
|
|||||||
|
|
||||||
private static async Task<CompatibilityAssetBundleManifest> GetManifestJson(string filepath)
|
private static async Task<CompatibilityAssetBundleManifest> GetManifestJson(string filepath)
|
||||||
{
|
{
|
||||||
var text = string.Empty;
|
var text = VFS.ReadTextFile(filepath);
|
||||||
|
|
||||||
using (var reader = File.OpenText($"{filepath}.json"))
|
/* we cannot parse directly as <string, BundleDetails>, because...
|
||||||
{
|
[Error : Unity Log] JsonSerializationException: Expected string when reading UnityEngine.Hash128 type, got 'StartObject' <>. Path '['assets/content/weapons/animations/simple_animations.bundle'].Hash', line 1, position 176.
|
||||||
text = await reader.ReadToEndAsync();
|
...so we need to first convert it to a slimmed-down type (BundleItem), then convert back to BundleDetails.
|
||||||
}
|
*/
|
||||||
|
var raw = JsonConvert.DeserializeObject<Dictionary<string, BundleItem>>(text);
|
||||||
|
var converted = raw.ToDictionary(GetPairKey, GetPairValue);
|
||||||
|
|
||||||
var data = JsonConvert.DeserializeObject<Dictionary<string, BundleItem>>(text).ToDictionary(GetPairKey, GetPairValue);
|
// initialize manifest
|
||||||
var manifest = ScriptableObject.CreateInstance<CompatibilityAssetBundleManifest>();
|
var manifest = ScriptableObject.CreateInstance<CompatibilityAssetBundleManifest>();
|
||||||
manifest.SetResults(data);
|
manifest.SetResults(converted);
|
||||||
|
|
||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Aki.Reflection.Patching;
|
using System;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
using Diz.DependencyManager;
|
using Diz.DependencyManager;
|
||||||
using UnityEngine.Build.Pipeline;
|
using UnityEngine.Build.Pipeline;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -26,21 +27,26 @@ namespace Aki.Custom.Patches
|
|||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
private static void PatchPostfix(object __instance, string key, string rootPath, CompatibilityAssetBundleManifest manifest, IBundleLock bundleLock)
|
private static void PatchPostfix(object __instance, string key, string rootPath, CompatibilityAssetBundleManifest manifest, IBundleLock bundleLock)
|
||||||
{
|
{
|
||||||
var path = rootPath + key;
|
var filepath = rootPath + key;
|
||||||
var dependencyKeys = manifest.GetDirectDependencies(key) ?? new string[0];
|
var dependencies = manifest.GetDirectDependencies(key) ?? Array.Empty<string>();
|
||||||
|
|
||||||
if (BundleManager.Bundles.TryGetValue(key, out BundleInfo bundle))
|
if (BundleManager.Bundles.TryGetValue(key, out BundleItem bundle))
|
||||||
{
|
{
|
||||||
dependencyKeys = (dependencyKeys.Length > 0) ? dependencyKeys.Union(bundle.DependencyKeys).ToArray() : bundle.DependencyKeys;
|
// server bundle
|
||||||
path = bundle.Path;
|
dependencies = (dependencies.Length > 0)
|
||||||
|
? dependencies.Union(bundle.Dependencies).ToArray()
|
||||||
|
: bundle.Dependencies;
|
||||||
|
|
||||||
|
// set path to either cache (HTTP) or mod (local)
|
||||||
|
filepath = BundleManager.GetBundlePath(bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = new EasyBundleHelper(__instance)
|
_ = new EasyBundleHelper(__instance)
|
||||||
{
|
{
|
||||||
Key = key,
|
Key = key,
|
||||||
Path = path,
|
Path = filepath,
|
||||||
KeyWithoutExtension = Path.GetFileNameWithoutExtension(key),
|
KeyWithoutExtension = Path.GetFileNameWithoutExtension(key),
|
||||||
DependencyKeys = dependencyKeys,
|
DependencyKeys = dependencies,
|
||||||
LoadState = new BindableState<ELoadState>(ELoadState.Unloaded, null),
|
LoadState = new BindableState<ELoadState>(ELoadState.Unloaded, null),
|
||||||
BundleLock = bundleLock
|
BundleLock = bundleLock
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using Comfort.Common;
|
using Comfort.Common;
|
||||||
using EFT;
|
using EFT;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
@ -14,8 +13,7 @@ namespace Aki.Custom.Patches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return PatchConstants.EftTypes.Single(x => x.Name == "LocalGame").BaseType // BaseLocalGame
|
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Stop));
|
||||||
.GetMethod("Stop", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look at BaseLocalGame<TPlayerOwner> and find a method named "Stop"
|
// Look at BaseLocalGame<TPlayerOwner> and find a method named "Stop"
|
||||||
@ -33,7 +31,7 @@ namespace Aki.Custom.Patches
|
|||||||
var player = Singleton<GameWorld>.Instance.MainPlayer;
|
var player = Singleton<GameWorld>.Instance.MainPlayer;
|
||||||
if (profileId == player?.Profile.Id)
|
if (profileId == player?.Profile.Id)
|
||||||
{
|
{
|
||||||
GClass2898.Instance.CloseAllScreensForced();
|
GClass3107.Instance.CloseAllScreensForced();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
46
project/Aki.Custom/Patches/FixBrokenSpawnOnSandboxPatch.cs
Normal file
46
project/Aki.Custom/Patches/FixBrokenSpawnOnSandboxPatch.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fixes the map sandbox from only spawning 1 bot at start of game as well as fixing no spawns till all bots are dead.
|
||||||
|
/// Remove once BSG decides to fix their map
|
||||||
|
/// </summary>
|
||||||
|
public class FixBrokenSpawnOnSandboxPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(GameWorld), nameof(GameWorld.OnGameStarted));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix()
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
if (gameWorld == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var playerLocation = gameWorld.MainPlayer.Location;
|
||||||
|
|
||||||
|
if (playerLocation == "Sandbox")
|
||||||
|
{
|
||||||
|
LocationScene.GetAll<BotZone>().ToList().First(x => x.name == "ZoneSandbox").MaxPersonsOnPatrol = GetMaxPatrolValueFromServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetMaxPatrolValueFromServer()
|
||||||
|
{
|
||||||
|
string json = RequestHandler.GetJson("/singleplayer/sandbox/maxpatrol");
|
||||||
|
return JsonConvert.DeserializeObject<int>(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
project/Aki.Custom/Patches/HalloweenExtractPatch.cs
Normal file
47
project/Aki.Custom/Patches/HalloweenExtractPatch.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.Interactive;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class HalloweenExtractPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BotHalloweenEvent), nameof(BotHalloweenEvent.RitualCompleted));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix()
|
||||||
|
{
|
||||||
|
GameWorld gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
Random random = new Random();
|
||||||
|
// Get all extracts the player has
|
||||||
|
List<ExfiltrationPoint> EligiblePoints = ExfiltrationControllerClass.Instance.EligiblePoints(gameWorld.MainPlayer.Profile).ToList();
|
||||||
|
List<ExfiltrationPoint> PointsToPickFrom = new List<ExfiltrationPoint>();
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var ExfilPoint in EligiblePoints)
|
||||||
|
{
|
||||||
|
if (ExfilPoint.Status == EExfiltrationStatus.RegularMode)
|
||||||
|
{
|
||||||
|
// Only add extracts that we want exludes car and timed extracts i think?
|
||||||
|
PointsToPickFrom.Add(ExfilPoint);
|
||||||
|
//ConsoleScreen.Log(ExfilPoint.Settings.Name + " Added to pool");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Randomly pick a extract from the list
|
||||||
|
int index = random.Next(PointsToPickFrom.Count);
|
||||||
|
string selectedExtract = PointsToPickFrom[index].Settings.Name;
|
||||||
|
//ConsoleScreen.Log(selectedExtract + " Picked for Extract");
|
||||||
|
|
||||||
|
ExfiltrationControllerClass.Instance.EventDisableAllExitsExceptOne(selectedExtract);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +1,16 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using EFT;
|
using EFT;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
public class IsEnemyPatch : ModulePatch
|
public class IsEnemyPatch : ModulePatch
|
||||||
{
|
{
|
||||||
private static Type _targetType;
|
|
||||||
private readonly string _targetMethodName = "IsEnemy";
|
|
||||||
|
|
||||||
public IsEnemyPatch()
|
|
||||||
{
|
|
||||||
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsTargetType(Type type)
|
|
||||||
{
|
|
||||||
if (type.GetMethod("AddEnemy") != null && type.GetMethod("AddEnemyGroupIfAllowed") != null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return _targetType.GetMethod(_targetMethodName);
|
return AccessTools.Method(typeof(BotsGroup), nameof(BotsGroup.IsEnemy));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -41,6 +22,17 @@ namespace Aki.Custom.Patches
|
|||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static bool PatchPrefix(ref bool __result, BotsGroup __instance, IPlayer requester)
|
private static bool PatchPrefix(ref bool __result, BotsGroup __instance, IPlayer requester)
|
||||||
{
|
{
|
||||||
|
if (__instance.InitialBotType == WildSpawnType.peacefullZryachiyEvent
|
||||||
|
|| __instance.InitialBotType == WildSpawnType.shooterBTR
|
||||||
|
|| __instance.InitialBotType == WildSpawnType.gifter
|
||||||
|
|| __instance.InitialBotType == WildSpawnType.sectantWarrior
|
||||||
|
|| __instance.InitialBotType == WildSpawnType.sectantPriest
|
||||||
|
|| __instance.InitialBotType == WildSpawnType.sectactPriestEvent
|
||||||
|
|| __instance.InitialBotType == WildSpawnType.ravangeZryachiyEvent)
|
||||||
|
{
|
||||||
|
return true; // Do original code
|
||||||
|
}
|
||||||
|
|
||||||
var isEnemy = false; // default not an enemy
|
var isEnemy = false; // default not an enemy
|
||||||
if (requester == null)
|
if (requester == null)
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
using Aki.Reflection.Utils;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using EFT;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
@ -13,8 +12,8 @@ namespace Aki.Custom.Patches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
var desiredType = PatchConstants.EftTypes.Single(x => x.Name == "LocalGame").BaseType; // BaseLocalGame
|
var desiredType = typeof(BaseLocalGame<GamePlayerOwner>);
|
||||||
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags).Single(x => IsTargetMethod(x)); // method_6
|
var desiredMethod = desiredType.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public).SingleCustom(IsTargetMethod); // method_6
|
||||||
|
|
||||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
@ -7,6 +7,7 @@ using EFT.UI.Matchmaker;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using EFT;
|
using EFT;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
@ -14,13 +15,8 @@ namespace Aki.Custom.Patches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
var desiredType = typeof(MatchmakerOfflineRaidScreen);
|
return AccessTools.GetDeclaredMethods(typeof(MatchmakerOfflineRaidScreen))
|
||||||
var desiredMethod = desiredType.GetMethod(nameof(MatchmakerOfflineRaidScreen.Show));
|
.SingleCustom(m => m.Name == nameof(MatchmakerOfflineRaidScreen.Show) && m.GetParameters().Length == 1);
|
||||||
|
|
||||||
Logger.LogDebug($"{GetType().Name} Type: {desiredType?.Name}");
|
|
||||||
Logger.LogDebug($"{GetType().Name} Method: {desiredMethod?.Name}");
|
|
||||||
|
|
||||||
return desiredMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
@ -49,6 +45,8 @@ namespace Aki.Custom.Patches
|
|||||||
raidSettings.WavesSettings.IsBosses = settings.BossEnabled;
|
raidSettings.WavesSettings.IsBosses = settings.BossEnabled;
|
||||||
raidSettings.BotSettings.IsScavWars = false;
|
raidSettings.BotSettings.IsScavWars = false;
|
||||||
raidSettings.WavesSettings.IsTaggedAndCursed = settings.TaggedAndCursed;
|
raidSettings.WavesSettings.IsTaggedAndCursed = settings.TaggedAndCursed;
|
||||||
|
raidSettings.TimeAndWeatherSettings.IsRandomWeather = settings.RandomWeather;
|
||||||
|
raidSettings.TimeAndWeatherSettings.IsRandomTime = settings.RandomTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
|
@ -2,23 +2,15 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using EFT.UI;
|
using EFT.UI;
|
||||||
using EFT.UI.Matchmaker;
|
using EFT.UI.Matchmaker;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
public class OfflineRaidSettingsMenuPatch : ModulePatch
|
public class OfflineRaidSettingsMenuPatch : ModulePatch
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// RaidSettingsWindow.Show()
|
|
||||||
/// </summary>
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
var desiredType = typeof(RaidSettingsWindow);
|
return AccessTools.Method(typeof(RaidSettingsWindow), nameof(RaidSettingsWindow.Show));
|
||||||
var desiredMethod = desiredType.GetMethod(nameof(RaidSettingsWindow.Show));
|
|
||||||
|
|
||||||
Logger.LogDebug($"{GetType().Name} Type: {desiredType.Name}");
|
|
||||||
Logger.LogDebug($"{GetType().Name} Method: {desiredMethod?.Name}");
|
|
||||||
|
|
||||||
return desiredMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.PrePatch;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
using Aki.Reflection.Utils;
|
||||||
using EFT;
|
using EFT;
|
||||||
using System;
|
using System;
|
||||||
@ -23,7 +24,7 @@ namespace Aki.Custom.Patches
|
|||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return _targetType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
|
return _targetType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -42,7 +43,7 @@ namespace Aki.Custom.Patches
|
|||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static bool PatchPrefix(BotOwner ___botOwner_0)
|
private static bool PatchPrefix(BotOwner ___botOwner_0)
|
||||||
{
|
{
|
||||||
if (___botOwner_0.IsRole((WildSpawnType)33) || ___botOwner_0.IsRole((WildSpawnType)34))
|
if (___botOwner_0.IsRole((WildSpawnType)AkiBotsPrePatcher.sptUsecValue) || ___botOwner_0.IsRole((WildSpawnType)AkiBotsPrePatcher.sptBearValue))
|
||||||
{
|
{
|
||||||
var healthController = ___botOwner_0.GetPlayer.ActiveHealthController;
|
var healthController = ___botOwner_0.GetPlayer.ActiveHealthController;
|
||||||
|
|
||||||
|
39
project/Aki.Custom/Patches/PreventClientModsPatch.cs
Normal file
39
project/Aki.Custom/Patches/PreventClientModsPatch.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.SinglePlayer.Utils.MainMenu;
|
||||||
|
using BepInEx.Bootstrap;
|
||||||
|
using BepInEx.Logging;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Patches.MainMenu
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Prevents loading of non-whitelisted client mods to minimize the amount of false issue reports being made during the public BE phase
|
||||||
|
/// </summary>
|
||||||
|
public class PreventClientModsPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(TarkovApplication), nameof(TarkovApplication.method_20));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void Prefix()
|
||||||
|
{
|
||||||
|
CheckForNonWhitelistedPlugins(Logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CheckForNonWhitelistedPlugins(ManualLogSource logger)
|
||||||
|
{
|
||||||
|
if (MenuNotificationManager.disallowedPlugins.Any())
|
||||||
|
{
|
||||||
|
logger.LogError($"{MenuNotificationManager.release.illegalPluginsLoadedText}\n{string.Join("\n", MenuNotificationManager.disallowedPlugins)}");
|
||||||
|
throw new Exception(MenuNotificationManager.release.illegalPluginsExceptionText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,14 +2,16 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using EFT;
|
using EFT;
|
||||||
using Aki.Reflection.Utils;
|
using HarmonyLib;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
public class QTEPatch : ModulePatch
|
public class QTEPatch : ModulePatch
|
||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod() => typeof(HideoutPlayerOwner).GetMethod(nameof(HideoutPlayerOwner.StopWorkout));
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(HideoutPlayerOwner), nameof(HideoutPlayerOwner.StopWorkout));
|
||||||
|
}
|
||||||
|
|
||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
private static void PatchPostfix(HideoutPlayerOwner __instance)
|
private static void PatchPostfix(HideoutPlayerOwner __instance)
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using Aki.Common.Http;
|
using Aki.Common.Http;
|
||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using EFT.InventoryLogic;
|
using EFT.InventoryLogic;
|
||||||
using EFT.UI.Ragfair;
|
using EFT.UI.Ragfair;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
@ -17,33 +17,32 @@ namespace Aki.Custom.Patches
|
|||||||
public RagfairFeePatch()
|
public RagfairFeePatch()
|
||||||
{
|
{
|
||||||
// Remember to update prefix parameter if below lines are broken
|
// Remember to update prefix parameter if below lines are broken
|
||||||
_ = nameof(GClass2860.IsAllSelectedItemSame);
|
_ = nameof(GClass3069.IsAllSelectedItemSame);
|
||||||
_ = nameof(GClass2860.AutoSelectSimilar);
|
_ = nameof(GClass3069.AutoSelectSimilar);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return typeof(AddOfferWindow).GetMethod("method_1", PatchConstants.PrivateFlags);
|
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculate tax to charge player and send to server before the offer is sent
|
/// Calculate tax to charge player and send to server before the offer is sent
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="___item_0">Item sold</param>
|
/// <param name="___item_0">Item sold</param>
|
||||||
/// <param name="___gclass2859_0">OfferItemCount</param>
|
/// <param name="___gclass3069_0">OfferItemCount</param>
|
||||||
/// <param name="___double_0">RequirementsPrice</param>
|
/// <param name="___double_0">RequirementsPrice</param>
|
||||||
/// <param name="___bool_0">SellInOnePiece</param>
|
/// <param name="___bool_0">SellInOnePiece</param>
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static void PatchPrefix(ref Item ___item_0, ref GClass2860 ___gclass2860_0, ref double ___double_0, ref bool ___bool_0)
|
private static void PatchPrefix(ref Item ___item_0, ref GClass3069 ___gclass3069_0, ref double ___double_0, ref bool ___bool_0)
|
||||||
{
|
{
|
||||||
RequestHandler.PutJson("/client/ragfair/offerfees", new
|
RequestHandler.PutJson("/client/ragfair/offerfees", new
|
||||||
{
|
{
|
||||||
id = ___item_0.Id,
|
id = ___item_0.Id,
|
||||||
tpl = ___item_0.TemplateId,
|
tpl = ___item_0.TemplateId,
|
||||||
count = ___gclass2860_0.OfferItemCount,
|
count = ___gclass3069_0.OfferItemCount,
|
||||||
fee = Mathf.CeilToInt((float)GClass1941.CalculateTaxPrice(___item_0, ___gclass2860_0.OfferItemCount, ___double_0, ___bool_0))
|
fee = Mathf.CeilToInt((float)GClass2089.CalculateTaxPrice(___item_0, ___gclass3069_0.OfferItemCount, ___double_0, ___bool_0))
|
||||||
}
|
}.ToJson());
|
||||||
.ToJson());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ using Aki.Custom.Models;
|
|||||||
using EFT.UI;
|
using EFT.UI;
|
||||||
using EFT.UI.Matchmaker;
|
using EFT.UI.Matchmaker;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
@ -14,19 +15,23 @@ namespace Aki.Custom.Patches
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class RaidSettingsWindowPatch : ModulePatch
|
public class RaidSettingsWindowPatch : ModulePatch
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Target method should have ~20 .UpdateValue() calls in it
|
||||||
|
/// </summary>
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
var desiredType = typeof(RaidSettingsWindow);
|
return AccessTools.Method(typeof(RaidSettingsWindow), nameof(RaidSettingsWindow.method_8));
|
||||||
var desiredMethod = desiredType.GetMethod("method_8", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
|
|
||||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
|
||||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
|
||||||
|
|
||||||
return desiredMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static bool PatchPreFix(UpdatableToggle ____enableBosses, UpdatableToggle ____scavWars, UpdatableToggle ____taggedAndCursed, DropDownBox ____aiDifficultyDropdown, DropDownBox ____aiAmountDropdown)
|
private static bool PatchPreFix(
|
||||||
|
UpdatableToggle ____enableBosses,
|
||||||
|
UpdatableToggle ____scavWars,
|
||||||
|
UpdatableToggle ____taggedAndCursed,
|
||||||
|
DropDownBox ____aiDifficultyDropdown,
|
||||||
|
DropDownBox ____aiAmountDropdown,
|
||||||
|
UpdatableToggle ____randomWeatherToggle,
|
||||||
|
UpdatableToggle ____randomTimeToggle)
|
||||||
{
|
{
|
||||||
var json = RequestHandler.GetJson("/singleplayer/settings/raid/menu");
|
var json = RequestHandler.GetJson("/singleplayer/settings/raid/menu");
|
||||||
var settings = Json.Deserialize<DefaultRaidSettings>(json);
|
var settings = Json.Deserialize<DefaultRaidSettings>(json);
|
||||||
@ -35,7 +40,10 @@ namespace Aki.Custom.Patches
|
|||||||
____scavWars.UpdateValue(false);
|
____scavWars.UpdateValue(false);
|
||||||
____taggedAndCursed.UpdateValue(settings.TaggedAndCursed);
|
____taggedAndCursed.UpdateValue(settings.TaggedAndCursed);
|
||||||
____aiDifficultyDropdown.UpdateValue((int)settings.AiDifficulty);
|
____aiDifficultyDropdown.UpdateValue((int)settings.AiDifficulty);
|
||||||
____aiAmountDropdown.UpdateValue((int)(settings.AiAmount));
|
____aiAmountDropdown.UpdateValue((int)settings.AiAmount);
|
||||||
|
|
||||||
|
____randomWeatherToggle.UpdateValue(settings.RandomWeather);
|
||||||
|
____randomTimeToggle.UpdateValue(settings.RandomTime);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using Comfort.Common;
|
using Comfort.Common;
|
||||||
using EFT;
|
using EFT;
|
||||||
using EFT.UI;
|
using EFT.UI;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
@ -11,13 +11,7 @@ namespace Aki.Custom.Patches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
var desiredType = typeof(RankPanel);
|
return AccessTools.Method(typeof(RankPanel), nameof(RankPanel.Show));
|
||||||
var desiredMethod = desiredType.GetMethod("Show", PatchConstants.PublicFlags);
|
|
||||||
|
|
||||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
|
||||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
|
||||||
|
|
||||||
return desiredMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
|
22
project/Aki.Custom/Patches/ResetTraderServicesPatch.cs
Normal file
22
project/Aki.Custom/Patches/ResetTraderServicesPatch.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.SinglePlayer.Utils.TraderServices;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
public class ResetTraderServicesPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Stop));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPrefix()
|
||||||
|
{
|
||||||
|
TraderServicesManager.Instance.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
project/Aki.Custom/Patches/ScavItemCheckmarkPatch.cs
Normal file
41
project/Aki.Custom/Patches/ScavItemCheckmarkPatch.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.UI.DragAndDrop;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
|
||||||
|
public class ScavItemCheckmarkPatch : ModulePatch
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This patch runs both inraid and on main Menu everytime the inventory is loaded
|
||||||
|
/// Aim is to let Scavs see what required items your PMC needs for quests like Live using the FiR status
|
||||||
|
/// </summary>
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(QuestItemViewPanel), nameof(QuestItemViewPanel.smethod_0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
private static void PatchPreFix(ref IEnumerable<QuestDataClass> quests)
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
|
||||||
|
if (gameWorld != null)
|
||||||
|
{
|
||||||
|
if (gameWorld.MainPlayer.Location != "hideout" && gameWorld.MainPlayer.Fraction == ETagStatus.Scav)
|
||||||
|
{
|
||||||
|
var pmcQuests = PatchConstants.BackEndSession.Profile.QuestsData;
|
||||||
|
var scavQuests = PatchConstants.BackEndSession.ProfileOfPet.QuestsData;
|
||||||
|
quests = pmcQuests.Concat(scavQuests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
using Aki.Reflection.Patching;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
using Aki.Reflection.Utils;
|
||||||
using EFT.UI.Matchmaker;
|
using EFT.UI.Matchmaker;
|
||||||
using System.Linq;
|
using HarmonyLib;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Aki.SinglePlayer.Patches.ScavMode
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copy over scav-only quests from PMC profile to scav profile on pre-raid screen
|
/// Copy over scav-only quests from PMC profile to scav profile on pre-raid screen
|
||||||
@ -14,13 +15,8 @@ namespace Aki.SinglePlayer.Patches.ScavMode
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
var desiredType = typeof(MatchmakerOfflineRaidScreen);
|
return AccessTools.GetDeclaredMethods(typeof(MatchmakerOfflineRaidScreen))
|
||||||
var desiredMethod = desiredType.GetMethod(nameof(MatchmakerOfflineRaidScreen.Show));
|
.SingleCustom(m => m.Name == nameof(MatchmakerOfflineRaidScreen.Show) && m.GetParameters().Length == 1);
|
||||||
|
|
||||||
Logger.LogDebug($"{GetType().Name} Type: {desiredType?.Name}");
|
|
||||||
Logger.LogDebug($"{GetType().Name} Method: {desiredMethod?.Name}");
|
|
||||||
|
|
||||||
return desiredMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using EFT.UI;
|
using EFT.UI;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using EFT;
|
||||||
|
using HarmonyLib;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
@ -11,15 +12,10 @@ namespace Aki.Custom.Patches
|
|||||||
{
|
{
|
||||||
private static PreloaderUI _preloader;
|
private static PreloaderUI _preloader;
|
||||||
|
|
||||||
static SessionIdPatch()
|
|
||||||
{
|
|
||||||
_preloader = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return PatchConstants.LocalGameType.BaseType.GetMethod("method_5", PatchConstants.PrivateFlags);
|
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.method_5));
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
private static void PatchPostfix()
|
private static void PatchPostfix()
|
||||||
|
68
project/Aki.Custom/Patches/SetLocationIdOnRaidStartPatch.cs
Normal file
68
project/Aki.Custom/Patches/SetLocationIdOnRaidStartPatch.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.Reflection.Utils;
|
||||||
|
using EFT;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Comfort.Common;
|
||||||
|
using System;
|
||||||
|
using static LocationSettingsClass;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Patches
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Local games do not set the locationId property like a network game does, `LocationId` is used by various bsg systems
|
||||||
|
/// e.g. btr/lightkeeper services
|
||||||
|
/// </summary>
|
||||||
|
public class SetLocationIdOnRaidStartPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static PropertyInfo _locationProperty;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
Type localGameBaseType = PatchConstants.LocalGameType.BaseType;
|
||||||
|
|
||||||
|
// At this point, gameWorld.MainPlayer isn't set, so we need to use the LocalGame's `Location_0` property
|
||||||
|
_locationProperty = localGameBaseType.GetProperties(PatchConstants.PublicDeclaredFlags)
|
||||||
|
.SingleCustom(x => x.PropertyType == typeof(Location));
|
||||||
|
|
||||||
|
// Find the TimeAndWeatherSettings handling method
|
||||||
|
var desiredMethod = localGameBaseType.GetMethods(PatchConstants.PublicDeclaredFlags).SingleOrDefault(IsTargetMethod);
|
||||||
|
|
||||||
|
Logger.LogDebug($"{GetType().Name} Type: {localGameBaseType?.Name}");
|
||||||
|
Logger.LogDebug($"{GetType().Name} Method: {desiredMethod?.Name}");
|
||||||
|
|
||||||
|
return desiredMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsTargetMethod(MethodInfo mi)
|
||||||
|
{
|
||||||
|
// Find method_3(TimeAndWeatherSettings timeAndWeather)
|
||||||
|
var parameters = mi.GetParameters();
|
||||||
|
return (parameters.Length == 1 && parameters[0].ParameterType == typeof(TimeAndWeatherSettings));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPostfix(AbstractGame __instance)
|
||||||
|
{
|
||||||
|
var gameWorld = Singleton<GameWorld>.Instance;
|
||||||
|
|
||||||
|
// EFT.HideoutGame is an internal class, so we can't do static type checking :(
|
||||||
|
if (__instance.GetType().Name.Contains("HideoutGame"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location location = _locationProperty.GetValue(__instance) as Location;
|
||||||
|
|
||||||
|
if (location == null)
|
||||||
|
{
|
||||||
|
Logger.LogError($"[SetLocationId] Failed to get location data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameWorld.LocationId = location.Id;
|
||||||
|
|
||||||
|
Logger.LogDebug($"[SetLocationId] Set locationId to: {location.Id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,9 +6,12 @@ using System.Reflection;
|
|||||||
|
|
||||||
namespace Aki.Custom.Patches
|
namespace Aki.Custom.Patches
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Redirect the settings data to save into the SPT folder, not app data
|
||||||
|
/// </summary>
|
||||||
public class SettingsLocationPatch : ModulePatch
|
public class SettingsLocationPatch : ModulePatch
|
||||||
{
|
{
|
||||||
private static string _sptPath = Path.Combine(Environment.CurrentDirectory, "user", "sptSettings");
|
private static readonly string _sptPath = Path.Combine(Environment.CurrentDirectory, "user", "sptSettings");
|
||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,6 @@ using Aki.Reflection.Utils;
|
|||||||
using Aki.Custom.Models;
|
using Aki.Custom.Models;
|
||||||
using EFT.UI;
|
using EFT.UI;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Comfort.Common;
|
using Comfort.Common;
|
||||||
|
|
||||||
@ -17,18 +16,9 @@ namespace Aki.Custom.Patches
|
|||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
try
|
return PatchConstants.EftTypes
|
||||||
{
|
.SingleCustom(x => x.GetField("Taxonomy", BindingFlags.Public | BindingFlags.Instance) != null)
|
||||||
return PatchConstants.EftTypes
|
|
||||||
.Single(x => x.GetField("Taxonomy", BindingFlags.Public | BindingFlags.Instance) != null)
|
|
||||||
.GetMethod("Create", BindingFlags.Public | BindingFlags.Static);
|
.GetMethod("Create", BindingFlags.Public | BindingFlags.Static);
|
||||||
}
|
|
||||||
catch (System.Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogInfo($"VersionLabelPatch failed {e.Message} {e.StackTrace} {e.InnerException.StackTrace}");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
@ -43,7 +33,9 @@ namespace Aki.Custom.Patches
|
|||||||
|
|
||||||
Traverse.Create(Singleton<PreloaderUI>.Instance).Field("_alphaVersionLabel").Property("LocalizationKey").SetValue("{0}");
|
Traverse.Create(Singleton<PreloaderUI>.Instance).Field("_alphaVersionLabel").Property("LocalizationKey").SetValue("{0}");
|
||||||
Traverse.Create(Singleton<PreloaderUI>.Instance).Field("string_2").SetValue(_versionLabel);
|
Traverse.Create(Singleton<PreloaderUI>.Instance).Field("string_2").SetValue(_versionLabel);
|
||||||
Traverse.Create(__result).Field("Major").SetValue(_versionLabel);
|
var major = Traverse.Create(__result).Field("Major");
|
||||||
|
var existingValue = major.GetValue();
|
||||||
|
major.SetValue($"{existingValue} {_versionLabel}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,50 +1,109 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Concurrent;
|
||||||
using System.Text.RegularExpressions;
|
using System.Threading.Tasks;
|
||||||
|
using BepInEx.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Aki.Common.Http;
|
using Aki.Common.Http;
|
||||||
using Aki.Common.Utils;
|
using Aki.Common.Utils;
|
||||||
using Aki.Custom.Models;
|
using Aki.Custom.Models;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace Aki.Custom.Utils
|
namespace Aki.Custom.Utils
|
||||||
{
|
{
|
||||||
public static class BundleManager
|
public static class BundleManager
|
||||||
{
|
{
|
||||||
public const string CachePath = "user/cache/bundles/";
|
private static ManualLogSource _logger;
|
||||||
public static Dictionary<string, BundleInfo> Bundles { get; private set; }
|
public static readonly ConcurrentDictionary<string, BundleItem> Bundles;
|
||||||
|
public static string CachePath;
|
||||||
|
|
||||||
static BundleManager()
|
static BundleManager()
|
||||||
{
|
{
|
||||||
Bundles = new Dictionary<string, BundleInfo>();
|
_logger = Logger.CreateLogSource(nameof(BundleManager));
|
||||||
|
Bundles = new ConcurrentDictionary<string, BundleItem>();
|
||||||
|
CachePath = "user/cache/bundles/";
|
||||||
|
}
|
||||||
|
|
||||||
if (VFS.Exists(CachePath))
|
public static string GetBundlePath(BundleItem bundle)
|
||||||
{
|
{
|
||||||
VFS.DeleteDirectory(CachePath);
|
return RequestHandler.IsLocal
|
||||||
}
|
? $"{bundle.ModPath}/bundles/{bundle.FileName}"
|
||||||
|
: CachePath + bundle.FileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void GetBundles()
|
public static void GetBundles()
|
||||||
{
|
{
|
||||||
|
// get bundles
|
||||||
var json = RequestHandler.GetJson("/singleplayer/bundles");
|
var json = RequestHandler.GetJson("/singleplayer/bundles");
|
||||||
var jArray = JArray.Parse(json);
|
var bundles = JsonConvert.DeserializeObject<BundleItem[]>(json);
|
||||||
|
|
||||||
foreach (var jObj in jArray)
|
// register bundles
|
||||||
|
var toDownload = new ConcurrentBag<BundleItem>();
|
||||||
|
|
||||||
|
Parallel.ForEach(bundles, (bundle) =>
|
||||||
{
|
{
|
||||||
var key = jObj["key"].ToString();
|
Bundles.TryAdd(bundle.FileName, bundle);
|
||||||
var path = jObj["path"].ToString();
|
|
||||||
var bundle = new BundleInfo(key, path, jObj["dependencyKeys"].ToObject<string[]>());
|
|
||||||
|
|
||||||
if (path.Contains("http"))
|
if (ShouldReaquire(bundle))
|
||||||
{
|
{
|
||||||
var filepath = CachePath + Regex.Split(path, "bundle/", RegexOptions.IgnoreCase)[1];
|
// mark for download
|
||||||
var data = RequestHandler.GetData(path, true);
|
toDownload.Add(bundle);
|
||||||
VFS.WriteFile(filepath, data);
|
|
||||||
bundle.Path = filepath;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Bundles.Add(key, bundle);
|
if (RequestHandler.IsLocal)
|
||||||
|
{
|
||||||
|
// loading from local mods
|
||||||
|
_logger.LogInfo("CACHE: Loading all bundles from mods on disk.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// download bundles
|
||||||
|
// NOTE: assumes bundle keys to be unique
|
||||||
|
Parallel.ForEach(toDownload, (bundle) =>
|
||||||
|
{
|
||||||
|
// download bundle
|
||||||
|
var filepath = GetBundlePath(bundle);
|
||||||
|
var data = RequestHandler.GetData($"/files/bundle/{bundle.FileName}");
|
||||||
|
VFS.WriteFile(filepath, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldReaquire(BundleItem bundle)
|
||||||
|
{
|
||||||
|
if (RequestHandler.IsLocal)
|
||||||
|
{
|
||||||
|
// only handle remote bundles
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
VFS.WriteTextFile(CachePath + "bundles.json", Json.Serialize<Dictionary<string, BundleInfo>>(Bundles));
|
// read cache
|
||||||
|
var filepath = CachePath + bundle.FileName;
|
||||||
|
|
||||||
|
if (VFS.Exists(filepath))
|
||||||
|
{
|
||||||
|
// calculate hash
|
||||||
|
var data = VFS.ReadFile(filepath);
|
||||||
|
var crc = Crc32.Compute(data);
|
||||||
|
|
||||||
|
if (crc == bundle.Crc)
|
||||||
|
{
|
||||||
|
// file is up-to-date
|
||||||
|
_logger.LogInfo($"CACHE: Loading locally {bundle.FileName}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// crc doesn't match, reaquire the file
|
||||||
|
_logger.LogInfo($"CACHE: Bundle is invalid, (re-)acquiring {bundle.FileName}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// file doesn't exist in cache
|
||||||
|
_logger.LogInfo($"CACHE: Bundle is missing, (re-)acquiring {bundle.FileName}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
80
project/Aki.Custom/Utils/Crc32.cs
Normal file
80
project/Aki.Custom/Utils/Crc32.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
By Senko-san (Merijn Hendriks)
|
||||||
|
Taken from https://github.com/spt-haru/haru
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Utils
|
||||||
|
{
|
||||||
|
public class Crc32
|
||||||
|
{
|
||||||
|
private static readonly uint[] _lookupTable = new uint[]
|
||||||
|
{
|
||||||
|
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419,
|
||||||
|
0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4,
|
||||||
|
0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07,
|
||||||
|
0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
|
||||||
|
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856,
|
||||||
|
0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
|
||||||
|
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
|
||||||
|
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
||||||
|
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
|
||||||
|
0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
|
||||||
|
0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599,
|
||||||
|
0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||||
|
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190,
|
||||||
|
0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
|
||||||
|
0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E,
|
||||||
|
0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
||||||
|
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
|
||||||
|
0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
|
||||||
|
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3,
|
||||||
|
0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
|
||||||
|
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
|
||||||
|
0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
|
||||||
|
0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010,
|
||||||
|
0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||||
|
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17,
|
||||||
|
0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
|
||||||
|
0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
|
||||||
|
0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
|
||||||
|
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344,
|
||||||
|
0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
|
||||||
|
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A,
|
||||||
|
0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
||||||
|
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1,
|
||||||
|
0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
|
||||||
|
0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
|
||||||
|
0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||||
|
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE,
|
||||||
|
0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
|
||||||
|
0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C,
|
||||||
|
0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
||||||
|
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B,
|
||||||
|
0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
|
||||||
|
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1,
|
||||||
|
0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
|
||||||
|
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
|
||||||
|
0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
|
||||||
|
0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66,
|
||||||
|
0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||||
|
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
|
||||||
|
0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
|
||||||
|
0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
|
||||||
|
0x2D02EF8D
|
||||||
|
};
|
||||||
|
|
||||||
|
public static uint Compute(byte[] bytes)
|
||||||
|
{
|
||||||
|
var crc = uint.MaxValue;
|
||||||
|
|
||||||
|
foreach (var octet in bytes)
|
||||||
|
{
|
||||||
|
crc = _lookupTable[(crc ^ octet) & byte.MaxValue] ^ crc >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ~crc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ namespace Aki.Custom.Utils
|
|||||||
{
|
{
|
||||||
public class EasyBundleHelper
|
public class EasyBundleHelper
|
||||||
{
|
{
|
||||||
private const BindingFlags _NonPublicInstanceflags = BindingFlags.Instance | BindingFlags.NonPublic;
|
private const BindingFlags NonPublicInstanceFlags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||||
private static readonly FieldInfo _pathField;
|
private static readonly FieldInfo _pathField;
|
||||||
private static readonly FieldInfo _keyWithoutExtensionField;
|
private static readonly FieldInfo _keyWithoutExtensionField;
|
||||||
private static readonly FieldInfo _bundleLockField;
|
private static readonly FieldInfo _bundleLockField;
|
||||||
@ -27,24 +27,28 @@ namespace Aki.Custom.Utils
|
|||||||
_ = nameof(IBundleLock.IsLocked);
|
_ = nameof(IBundleLock.IsLocked);
|
||||||
_ = nameof(BindableState.Bind);
|
_ = nameof(BindableState.Bind);
|
||||||
|
|
||||||
// Class can be found as a private array inside EasyAssets.cs, next to DependencyGraph<IEasyBundle>
|
Type = PatchConstants.EftTypes.SingleCustom(x => !x.IsInterface && x.GetProperty("SameNameAsset", PatchConstants.PublicDeclaredFlags) != null);
|
||||||
Type = PatchConstants.EftTypes.Single(x => x.GetMethod("set_SameNameAsset", _NonPublicInstanceflags) != null);
|
|
||||||
|
|
||||||
_pathField = Type.GetField("string_1", _NonPublicInstanceflags);
|
_pathField = Type.GetField("string_1", NonPublicInstanceFlags);
|
||||||
_keyWithoutExtensionField = Type.GetField("string_0", _NonPublicInstanceflags);
|
_keyWithoutExtensionField = Type.GetField("string_0", NonPublicInstanceFlags);
|
||||||
_bundleLockField = Type.GetFields(_NonPublicInstanceflags).FirstOrDefault(x => x.FieldType == typeof(IBundleLock));
|
_bundleLockField = Type.GetFields(NonPublicInstanceFlags).FirstOrDefault(x => x.FieldType == typeof(IBundleLock));
|
||||||
_dependencyKeysProperty = Type.GetProperty("DependencyKeys");
|
_dependencyKeysProperty = Type.GetProperty("DependencyKeys");
|
||||||
_keyProperty = Type.GetProperty("Key");
|
_keyProperty = Type.GetProperty("Key");
|
||||||
_loadStateProperty = Type.GetProperty("LoadState");
|
_loadStateProperty = Type.GetProperty("LoadState");
|
||||||
|
|
||||||
// Function with 0 params and returns task (usually method_0())
|
// Function with 0 params and returns task (usually method_0())
|
||||||
var possibleMethods = Type.GetMethods(_NonPublicInstanceflags).Where(x => x.GetParameters().Length == 0 && x.ReturnType == typeof(Task));
|
var possibleMethods = Type.GetMethods(PatchConstants.PublicDeclaredFlags).Where(x => x.GetParameters().Length == 0 && x.ReturnType == typeof(Task)).ToArray();
|
||||||
if (possibleMethods.Count() > 1)
|
if (possibleMethods.Length > 1)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Unable to find desired method as there are multiple possible matches: {string.Join(",", possibleMethods.Select(x => x.Name))}");
|
throw new Exception($"Unable to find the Loading Coroutine method as there are multiple possible matches: {string.Join(",", possibleMethods.Select(x => x.Name))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadingCoroutineMethod = possibleMethods.SingleOrDefault();
|
if (possibleMethods.Length == 0)
|
||||||
|
{
|
||||||
|
throw new Exception("Unable to find the Loading Coroutine method as there are no matches");
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadingCoroutineMethod = possibleMethods.Single();
|
||||||
}
|
}
|
||||||
|
|
||||||
public EasyBundleHelper(object easyBundle)
|
public EasyBundleHelper(object easyBundle)
|
||||||
|
169
project/Aki.Custom/Utils/MenuNotificationManager.cs
Normal file
169
project/Aki.Custom/Utils/MenuNotificationManager.cs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Common.Utils;
|
||||||
|
using Aki.Custom.Models;
|
||||||
|
using Aki.SinglePlayer.Patches.MainMenu;
|
||||||
|
using BepInEx.Bootstrap;
|
||||||
|
using BepInEx.Logging;
|
||||||
|
using Comfort.Common;
|
||||||
|
using EFT.UI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Aki.SinglePlayer.Utils.MainMenu
|
||||||
|
{
|
||||||
|
public class MenuNotificationManager : MonoBehaviour
|
||||||
|
{
|
||||||
|
public static string sptVersion;
|
||||||
|
public static string commitHash;
|
||||||
|
internal static HashSet<string> whitelistedPlugins = new HashSet<string>
|
||||||
|
{
|
||||||
|
"com.spt-aki.core",
|
||||||
|
"com.spt-aki.custom",
|
||||||
|
"com.spt-aki.debugging",
|
||||||
|
"com.spt-aki.singleplayer",
|
||||||
|
"com.bepis.bepinex.configurationmanager",
|
||||||
|
"com.terkoiz.freecam",
|
||||||
|
"com.sinai.unityexplorer",
|
||||||
|
"com.cwx.debuggingtool-dxyz",
|
||||||
|
"com.cwx.debuggingtool",
|
||||||
|
"xyz.drakia.botdebug",
|
||||||
|
"com.kobrakon.camunsnap",
|
||||||
|
"RuntimeUnityEditor"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string[] disallowedPlugins;
|
||||||
|
internal static ReleaseResponse release;
|
||||||
|
private bool _isBetaDisclaimerOpen = false;
|
||||||
|
private ManualLogSource Logger;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(MenuNotificationManager));
|
||||||
|
|
||||||
|
var versionJson = RequestHandler.GetJson("/singleplayer/settings/version");
|
||||||
|
sptVersion = Json.Deserialize<VersionResponse>(versionJson).Version;
|
||||||
|
commitHash = sptVersion?.Trim()?.Split(' ')?.Last() ?? "";
|
||||||
|
|
||||||
|
var releaseJson = RequestHandler.GetJson("/singleplayer/release");
|
||||||
|
release = Json.Deserialize<ReleaseResponse>(releaseJson);
|
||||||
|
|
||||||
|
SetVersionPref();
|
||||||
|
|
||||||
|
// Enable the watermark if this is a bleeding edge build.
|
||||||
|
// Enabled in start to allow time for the request containing the bool to process.
|
||||||
|
if (release.isBeta)
|
||||||
|
{
|
||||||
|
new BetaLogoPatch().Enable();
|
||||||
|
new BetaLogoPatch2().Enable();
|
||||||
|
new BetaLogoPatch3().Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
disallowedPlugins = Chainloader.PluginInfos.Values
|
||||||
|
.Select(pi => pi.Metadata.GUID).Except(whitelistedPlugins).ToArray();
|
||||||
|
|
||||||
|
// Prevent client mods if the server is built with mods disabled
|
||||||
|
if (!release.isModdable)
|
||||||
|
{
|
||||||
|
new PreventClientModsPatch().Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (release.isBeta && PlayerPrefs.GetInt("SPT_AcceptedBETerms") == 1)
|
||||||
|
{
|
||||||
|
Logger.LogInfo(release.betaDisclaimerAcceptText);
|
||||||
|
ServerLog.Info("Aki.Custom", release.betaDisclaimerAcceptText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (release.isModded && release.isBeta && release.isModdable)
|
||||||
|
{
|
||||||
|
commitHash += $"\n {release.serverModsLoadedDebugText}";
|
||||||
|
ServerLog.Warn("Aki.Custom", release.serverModsLoadedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disallowedPlugins.Any() && release.isBeta && release.isModdable)
|
||||||
|
{
|
||||||
|
commitHash += $"\n {release.clientModsLoadedDebugText}";
|
||||||
|
ServerLog.Warn("Aki.Custom", $"{release.clientModsLoadedText}\n{string.Join("\n", disallowedPlugins)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (sptVersion == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowBetaMessage();
|
||||||
|
ShowReleaseNotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the beta message
|
||||||
|
// if mods are enabled show that mods are loaded in the message.
|
||||||
|
private void ShowBetaMessage()
|
||||||
|
{
|
||||||
|
if (Singleton<PreloaderUI>.Instantiated && ShouldShowBetaMessage())
|
||||||
|
{
|
||||||
|
Singleton<PreloaderUI>.Instance.ShowCriticalErrorScreen(sptVersion, release.betaDisclaimerText, ErrorScreen.EButtonType.OkButton, release.betaDisclaimerTimeoutDelay, new Action(OnMessageAccepted), new Action(OnTimeOut));
|
||||||
|
_isBetaDisclaimerOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the release notes.
|
||||||
|
private void ShowReleaseNotes()
|
||||||
|
{
|
||||||
|
if (Singleton<PreloaderUI>.Instantiated && ShouldShowReleaseNotes())
|
||||||
|
{
|
||||||
|
Singleton<PreloaderUI>.Instance.ShowCriticalErrorScreen(sptVersion, release.releaseSummaryText, ErrorScreen.EButtonType.OkButton, 36000, null, null);
|
||||||
|
PlayerPrefs.SetInt("SPT_ShownReleaseNotes", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User accepted the terms, allow to continue.
|
||||||
|
private void OnMessageAccepted()
|
||||||
|
{
|
||||||
|
Logger.LogInfo(release.betaDisclaimerAcceptText);
|
||||||
|
PlayerPrefs.SetInt("SPT_AcceptedBETerms", 1);
|
||||||
|
_isBetaDisclaimerOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user doesnt accept the message "Ok" then the game will close.
|
||||||
|
private void OnTimeOut()
|
||||||
|
{
|
||||||
|
Application.Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stores the current build in the registry to check later
|
||||||
|
// Return true if changed, false if not
|
||||||
|
private void SetVersionPref()
|
||||||
|
{
|
||||||
|
if (GetVersionPref() == string.Empty || GetVersionPref() != sptVersion)
|
||||||
|
{
|
||||||
|
PlayerPrefs.SetString("SPT_Version", sptVersion);
|
||||||
|
|
||||||
|
// 0 val used to indicate false, 1 val used to indicate true
|
||||||
|
PlayerPrefs.SetInt("SPT_AcceptedBETerms", 0);
|
||||||
|
PlayerPrefs.SetInt("SPT_ShownReleaseNotes", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves the current build from the registry to check against the current build
|
||||||
|
// If this is the first run and no entry exists returns an empty string
|
||||||
|
private string GetVersionPref()
|
||||||
|
{
|
||||||
|
return PlayerPrefs.GetString("SPT_Version", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should we show the message, only show if first run or if build has changed
|
||||||
|
private bool ShouldShowBetaMessage()
|
||||||
|
{
|
||||||
|
return PlayerPrefs.GetInt("SPT_AcceptedBETerms") == 0 && release.isBeta && !_isBetaDisclaimerOpen ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should we show the release notes, only show on first run or if build has changed
|
||||||
|
private bool ShouldShowReleaseNotes()
|
||||||
|
{
|
||||||
|
return PlayerPrefs.GetInt("SPT_ShownReleaseNotes") == 0 && !_isBetaDisclaimerOpen && release.releaseSummaryText != string.Empty ? true : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
project/Aki.Custom/Utils/MessageBoxHelper.cs
Normal file
56
project/Aki.Custom/Utils/MessageBoxHelper.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Aki.Custom.Utils
|
||||||
|
{
|
||||||
|
public class MessageBoxHelper
|
||||||
|
{
|
||||||
|
public enum MessageBoxType : uint
|
||||||
|
{
|
||||||
|
ABORTRETRYIGNORE = (uint)(0x00000002L | 0x00000010L),
|
||||||
|
CANCELTRYCONTINUE = (uint)(0x00000006L | 0x00000030L),
|
||||||
|
HELP = (uint)(0x00004000L | 0x00000040L),
|
||||||
|
OK = (uint)(0x00000000L | 0x00000040L),
|
||||||
|
OKCANCEL = (uint)(0x00000001L | 0x00000040L),
|
||||||
|
RETRYCANCEL = (uint)0x00000005L,
|
||||||
|
YESNO = (uint)(0x00000004L | 0x00000040L),
|
||||||
|
YESNOCANCEL = (uint)(0x00000003L | 0x00000040L),
|
||||||
|
DEFAULT = (uint)(0x00000000L | 0x00000010L)
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MessageBoxResult
|
||||||
|
{
|
||||||
|
ERROR = -1,
|
||||||
|
OK = 1,
|
||||||
|
CANCEL = 2,
|
||||||
|
ABORT = 3,
|
||||||
|
RETRY = 4,
|
||||||
|
IGNORE = 5,
|
||||||
|
YES = 6,
|
||||||
|
NO = 7,
|
||||||
|
TRY_AGAIN = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr GetActiveWindow();
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
static extern int MessageBox(IntPtr hwnd, String lpText, String lpCaption, uint uType);
|
||||||
|
|
||||||
|
public static IntPtr GetWindowHandle()
|
||||||
|
{
|
||||||
|
return GetActiveWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MessageBoxResult Show(string text, string caption, MessageBoxType type = MessageBoxType.DEFAULT)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (MessageBoxResult)MessageBox(GetWindowHandle(), text, caption, (uint)type); ;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return MessageBoxResult.ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,19 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net471</TargetFramework>
|
||||||
<AssemblyName>aki-debugging</AssemblyName>
|
<AssemblyName>aki-debugging</AssemblyName>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Company>Aki</Company>
|
<Company>Aki</Company>
|
||||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
<Copyright>Copyright @ Aki 2024</Copyright>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\hollowed.dll" Private="False" />
|
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\hollowed.dll" Private="False" />
|
||||||
|
<Reference Include="bsg.console.core" HintPath="..\Shared\Managed\bsg.console.core.dll" Private="False" />
|
||||||
<Reference Include="Sirenix.Serialization" HintPath="..\Shared\Managed\Sirenix.Serialization.dll" Private="False" />
|
<Reference Include="Sirenix.Serialization" HintPath="..\Shared\Managed\Sirenix.Serialization.dll" Private="False" />
|
||||||
<Reference Include="Unity.TextMeshPro" HintPath="..\Shared\Managed\Unity.TextMeshPro.dll" Private="False" />
|
<Reference Include="Unity.TextMeshPro" HintPath="..\Shared\Managed\Unity.TextMeshPro.dll" Private="False" />
|
||||||
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||||
@ -20,10 +22,15 @@
|
|||||||
<Reference Include="UnityEngine.PhysicsModule" HintPath="..\Shared\Managed\UnityEngine.PhysicsModule.dll" Private="False" />
|
<Reference Include="UnityEngine.PhysicsModule" HintPath="..\Shared\Managed\UnityEngine.PhysicsModule.dll" Private="False" />
|
||||||
<Reference Include="UnityEngine.UI" HintPath="..\Shared\Managed\UnityEngine.UI.dll" Private="False" />
|
<Reference Include="UnityEngine.UI" HintPath="..\Shared\Managed\UnityEngine.UI.dll" Private="False" />
|
||||||
<Reference Include="UnityEngine.UIModule" HintPath="..\Shared\Managed\UnityEngine.UIModule.dll" Private="False" />
|
<Reference Include="UnityEngine.UIModule" HintPath="..\Shared\Managed\UnityEngine.UIModule.dll" Private="False" />
|
||||||
|
<Reference Include="Comfort.Common" HintPath="..\Shared\Managed\Comfort.dll" Private="False" />
|
||||||
|
<Reference Include="DissonanceVoip" HintPath="..\Shared\Managed\DissonanceVoip.dll" Private="False" />
|
||||||
|
<Reference Include="Nlog" HintPath="..\Shared\Managed\NLog.dll" Private="False" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.Custom\Aki.Custom.csproj" />
|
||||||
|
<ProjectReference Include="..\Aki.PrePatch\Aki.PrePatch.csproj" />
|
||||||
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using Aki.Common;
|
using Aki.Common;
|
||||||
|
using Aki.Common.Http;
|
||||||
|
using Aki.Common.Utils;
|
||||||
using Aki.Debugging.Patches;
|
using Aki.Debugging.Patches;
|
||||||
using BepInEx;
|
using BepInEx;
|
||||||
|
|
||||||
@ -8,15 +10,27 @@ namespace Aki.Debugging
|
|||||||
[BepInPlugin("com.spt-aki.debugging", "AKI.Debugging", AkiPluginInfo.PLUGIN_VERSION)]
|
[BepInPlugin("com.spt-aki.debugging", "AKI.Debugging", AkiPluginInfo.PLUGIN_VERSION)]
|
||||||
public class AkiDebuggingPlugin : BaseUnityPlugin
|
public class AkiDebuggingPlugin : BaseUnityPlugin
|
||||||
{
|
{
|
||||||
|
public static LoggingLevelResponse logLevel;
|
||||||
|
|
||||||
public void Awake()
|
public void Awake()
|
||||||
{
|
{
|
||||||
Logger.LogInfo("Loading: Aki.Debugging");
|
Logger.LogInfo("Loading: Aki.Debugging");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// new CoordinatesPatch().Enable();
|
|
||||||
new EndRaidDebug().Enable();
|
new EndRaidDebug().Enable();
|
||||||
}
|
new LoggerClassLogPatch().Enable();
|
||||||
|
// new CoordinatesPatch().Enable();
|
||||||
|
// new StaticLootDumper().Enable();
|
||||||
|
// new ExfilDumper().Enable();
|
||||||
|
|
||||||
|
// BTR debug command patches, can be disabled later
|
||||||
|
//new BTRDebugCommandPatch().Enable();
|
||||||
|
//new BTRDebugDataPatch().Enable();
|
||||||
|
|
||||||
|
//new PMCBotSpawnLocationPatch().Enable();
|
||||||
|
//new ReloadClientPatch().Enable();
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError($"{GetType().Name}: {ex}");
|
Logger.LogError($"{GetType().Name}: {ex}");
|
||||||
@ -25,5 +39,11 @@ namespace Aki.Debugging
|
|||||||
|
|
||||||
Logger.LogInfo("Completed: Aki.Debugging");
|
Logger.LogInfo("Completed: Aki.Debugging");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
var loggingJson = RequestHandler.GetJson("/singleplayer/enableBSGlogging");
|
||||||
|
logLevel = Json.Deserialize<LoggingLevelResponse>(loggingJson);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
49
project/Aki.Debugging/Patches/BTRDebugCommandPatch.cs
Normal file
49
project/Aki.Debugging/Patches/BTRDebugCommandPatch.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using Aki.Custom.BTR.Patches;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using Aki.SinglePlayer.Utils.TraderServices;
|
||||||
|
using EFT;
|
||||||
|
using EFT.UI;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System.Reflection;
|
||||||
|
using DialogControlClass = GClass1957;
|
||||||
|
|
||||||
|
namespace Aki.Debugging.Patches
|
||||||
|
{
|
||||||
|
// Enable the `debug_show_dialog_screen` command, and custom `btr_deliver_items` command
|
||||||
|
internal class BTRDebugCommandPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(ConsoleScreen), nameof(ConsoleScreen.InitConsole));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
internal static void PatchPostfix()
|
||||||
|
{
|
||||||
|
ConsoleScreen.Processor.RegisterCommandGroup<DialogControlClass>();
|
||||||
|
ConsoleScreen.Processor.RegisterCommand("btr_deliver_items", new System.Action(BtrDeliverItemsCommand));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom command to force item extraction sending
|
||||||
|
public static void BtrDeliverItemsCommand()
|
||||||
|
{
|
||||||
|
BTREndRaidItemDeliveryPatch.PatchPrefix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When running the `debug_show_dialog_screen` command, fetch the service data first, and force debug off
|
||||||
|
public class BTRDebugDataPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(DialogControlClass), nameof(DialogControlClass.ShowDialogScreen));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
internal static void PatchPrefix(Profile.ETraderServiceSource traderServiceSourceType, ref bool useDebugData)
|
||||||
|
{
|
||||||
|
useDebugData = false;
|
||||||
|
TraderServicesManager.Instance.GetTraderServicesDataFromServer(Profile.TraderInfo.TraderServiceToId[traderServiceSourceType]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,51 +1,47 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using EFT;
|
using EFT;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
namespace Aki.Debugging.Patches
|
namespace Aki.Debugging.Patches
|
||||||
{
|
{
|
||||||
public class CoordinatesPatch : ModulePatch
|
public class CoordinatesPatch : ModulePatch
|
||||||
{
|
{
|
||||||
private static TextMeshProUGUI _alphaLabel;
|
private static TextMeshProUGUI _alphaLabel;
|
||||||
private static PropertyInfo _playerProperty;
|
|
||||||
|
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
var localGameBaseType = PatchConstants.LocalGameType.BaseType;
|
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Update));
|
||||||
_playerProperty = localGameBaseType.GetProperty("PlayerOwner", BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
return localGameBaseType.GetMethod("Update", PatchConstants.PrivateFlags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static void PatchPrefix(object __instance)
|
private static void PatchPrefix(BaseLocalGame<GamePlayerOwner> __instance)
|
||||||
{
|
{
|
||||||
if (Input.GetKeyDown(KeyCode.LeftControl))
|
if (!Input.GetKeyDown(KeyCode.LeftControl)) return;
|
||||||
|
|
||||||
|
if (_alphaLabel == null)
|
||||||
{
|
{
|
||||||
if (_alphaLabel == null)
|
_alphaLabel = GameObject.Find("AlphaLabel").GetComponent<TextMeshProUGUI>();
|
||||||
{
|
_alphaLabel.color = Color.green;
|
||||||
_alphaLabel = GameObject.Find("AlphaLabel").GetComponent<TextMeshProUGUI>();
|
_alphaLabel.fontSize = 22;
|
||||||
_alphaLabel.color = Color.green;
|
_alphaLabel.font = Resources.Load<TMP_FontAsset>("Fonts & Materials/ARIAL SDF");
|
||||||
_alphaLabel.fontSize = 22;
|
|
||||||
_alphaLabel.font = Resources.Load<TMP_FontAsset>("Fonts & Materials/ARIAL SDF");
|
|
||||||
}
|
|
||||||
|
|
||||||
var playerOwner = (GamePlayerOwner)_playerProperty.GetValue(__instance);
|
|
||||||
var aiming = LookingRaycast(playerOwner.Player);
|
|
||||||
|
|
||||||
if (_alphaLabel != null)
|
|
||||||
{
|
|
||||||
_alphaLabel.text = $"Looking at: [{aiming.x}, {aiming.y}, {aiming.z}]";
|
|
||||||
Logger.LogInfo(_alphaLabel.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
var position = playerOwner.transform.position;
|
|
||||||
var rotation = playerOwner.transform.rotation.eulerAngles;
|
|
||||||
Logger.LogInfo($"Character position: [{position.x},{position.y},{position.z}] | Rotation: [{rotation.x},{rotation.y},{rotation.z}]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var playerOwner = __instance.PlayerOwner;
|
||||||
|
var aiming = LookingRaycast(playerOwner.Player);
|
||||||
|
|
||||||
|
if (_alphaLabel != null)
|
||||||
|
{
|
||||||
|
_alphaLabel.text = $"Looking at: [{aiming.x}, {aiming.y}, {aiming.z}]";
|
||||||
|
Logger.LogInfo(_alphaLabel.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
var position = playerOwner.transform.position;
|
||||||
|
var rotation = playerOwner.transform.rotation.eulerAngles;
|
||||||
|
Logger.LogInfo($"Character position: [{position.x},{position.y},{position.z}] | Rotation: [{rotation.x},{rotation.y},{rotation.z}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vector3 LookingRaycast(Player player)
|
public static Vector3 LookingRaycast(Player player)
|
||||||
|
21
project/Aki.Debugging/Patches/DataHandlerDebugPatch.cs
Normal file
21
project/Aki.Debugging/Patches/DataHandlerDebugPatch.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using Aki.Reflection.Patching;
|
||||||
|
using HarmonyLib;
|
||||||
|
|
||||||
|
namespace Aki.Debugging.Patches
|
||||||
|
{
|
||||||
|
public class DataHandlerDebugPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(DataHandler), nameof(DataHandler.method_5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
private static void PatchPrefix(ref string __result)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"response json: ${__result}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
using Aki.Reflection.Patching;
|
using Aki.Reflection.Patching;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Aki.Reflection.Utils;
|
|
||||||
using BepInEx.Logging;
|
using BepInEx.Logging;
|
||||||
using EFT;
|
using EFT;
|
||||||
using EFT.UI;
|
using EFT.UI;
|
||||||
|
using HarmonyLib;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
|
|
||||||
namespace Aki.Debugging.Patches
|
namespace Aki.Debugging.Patches
|
||||||
@ -12,12 +12,12 @@ namespace Aki.Debugging.Patches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return typeof(TraderCard).GetMethod("method_0", PatchConstants.PrivateFlags);
|
return AccessTools.Method(typeof(TraderCard), nameof(TraderCard.method_0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
private static bool PatchPreFix(ref LocalizedText ____nickName, ref TMP_Text ____standing,
|
private static bool PatchPreFix(ref LocalizedText ____nickName, ref TMP_Text ____standing,
|
||||||
ref RankPanel ____rankPanel, ref Profile.GClass1625 ___gclass1625_0)
|
ref RankPanel ____rankPanel, ref Profile.TraderInfo ___traderInfo_0)
|
||||||
{
|
{
|
||||||
if (____nickName.LocalizationKey == null)
|
if (____nickName.LocalizationKey == null)
|
||||||
{
|
{
|
||||||
@ -37,16 +37,16 @@ namespace Aki.Debugging.Patches
|
|||||||
return false; // skip original
|
return false; // skip original
|
||||||
}
|
}
|
||||||
|
|
||||||
if (___gclass1625_0?.LoyaltyLevel == null)
|
if (___traderInfo_0?.LoyaltyLevel == null)
|
||||||
{
|
{
|
||||||
ConsoleScreen.LogError("This Shouldn't happen!! Please report this in discord");
|
ConsoleScreen.LogError("This Shouldn't happen!! Please report this in discord");
|
||||||
Logger.Log(LogLevel.Error, "[AKI] _gclass1618_0 or _gclass1618_0.LoyaltyLevel was null");
|
Logger.Log(LogLevel.Error, "[AKI] ___traderInfo_0 or ___traderInfo_0.LoyaltyLevel was null");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (___gclass1625_0?.MaxLoyaltyLevel == null)
|
if (___traderInfo_0?.MaxLoyaltyLevel == null)
|
||||||
{
|
{
|
||||||
ConsoleScreen.LogError("This Shouldn't happen!! Please report this in discord");
|
ConsoleScreen.LogError("This Shouldn't happen!! Please report this in discord");
|
||||||
Logger.Log(LogLevel.Error, "[AKI] _gclass1618_0 or _gclass1618_0.MaxLoyaltyLevel was null");
|
Logger.Log(LogLevel.Error, "[AKI] ___traderInfo_0 or ___traderInfo_0.MaxLoyaltyLevel was null");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user