mirror of
https://github.com/sp-tarkov/modules.git
synced 2025-02-12 14:10:44 -05:00
Add repo
This commit is contained in:
commit
b3ce0ec36f
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
355
.gitignore
vendored
Normal file
355
.gitignore
vendored
Normal file
@ -0,0 +1,355 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# Aki
|
||||
**/Build/
|
||||
**/Shared/Managed/*
|
||||
**/tools/
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Bb]in/[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
32
LICENSE.md
Normal file
32
LICENSE.md
Normal file
@ -0,0 +1,32 @@
|
||||
# NCSA Open Source License
|
||||
|
||||
Copyright (c) 2022 Merijn Hendriks. All rights reserved.
|
||||
|
||||
Developed by: Merijn Hendriks
|
||||
SPT-AKI
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
with the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimers.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimers in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the names of Merijn Hendriks, SPT-AKI, nor the names of
|
||||
its contributors may be used to endorse or promote products derived from
|
||||
this Software without specific prior written permission.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS WITH THE SOFTWARE.
|
31
README.md
Normal file
31
README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Modules
|
||||
|
||||
BepInEx plugins to alter Escape From Tarkov's behaviour
|
||||
|
||||
**Project** | **Function**
|
||||
------------------ | --------------------------------------------
|
||||
Aki.Build | Build script
|
||||
Aki.Bundles | External bundle loader
|
||||
Aki.Common | Common utilities used across projects
|
||||
Aki.Core | Required patches to start the game
|
||||
Aki.Custom | SPT-AKI enhancements to EFT
|
||||
Aki.Debugging | Debug utilities (disabled in release builds)
|
||||
Aki.Reflection | Reflection utilities used across the project
|
||||
Aki.SinglePlayer | Simulating online game while offline
|
||||
|
||||
## Requirements
|
||||
|
||||
- Escape From Tarkov 22032
|
||||
- BepInEx 5.4.19
|
||||
- Visual Studio Code
|
||||
- .NET 6 SDK
|
||||
|
||||
## Setup
|
||||
|
||||
Copy-paste Live EFT's `EscapeFromTarkov_Data/Managed/` folder to into Modules' `Project/Shared/` folder
|
||||
|
||||
## Build
|
||||
|
||||
1. File > Open Workspace > Modules.code-workspace
|
||||
2. Terminal > Run Build Task...
|
||||
3. Copy-paste content inside `Build` into `%gamedir%`, overwrite when prompted.
|
128
docs/packetsniffer.md
Normal file
128
docs/packetsniffer.md
Normal file
@ -0,0 +1,128 @@
|
||||
# Packet Sniffer
|
||||
|
||||
References are based on version 0.12.12.15.17566
|
||||
|
||||
## Requirements
|
||||
|
||||
- de4dot
|
||||
- dnspy
|
||||
|
||||
## Deobfuscation
|
||||
|
||||
```cs
|
||||
// Token: 0x0600D716 RID: 55062 RVA: 0x00127E88 File Offset: 0x00126088
|
||||
Class2082.smethod_0()
|
||||
{
|
||||
return (string)((Hashtable)AppDomain.CurrentDomain.GetData(Class2082.string_0))[int_0];
|
||||
}
|
||||
```
|
||||
|
||||
```cmd
|
||||
de4dot-x64.exe Assembly-CSharp.dll
|
||||
de4dot-x64.exe --un-name "!^<>[a-z0-9]$&!^<>[a-z0-9]__.*$&![A-Z][A-Z]\$<>.*$&^[a-zA-Z_<{$][a-zA-Z_0-9<>{}$.`-]*$" "Assembly-CSharp-cleaned.dll" --strtyp delegate --strtok 0x0600D716
|
||||
pause
|
||||
```
|
||||
|
||||
### Fix ResolutionScope error
|
||||
|
||||
1. DnSpy > File > Open... > `Assembly-CSharp-cleaned-cleaned.dll`
|
||||
2. DnSpy > File > Save module.. > OK
|
||||
|
||||
## Modifications
|
||||
|
||||
### Assembly-CSharp.dll
|
||||
|
||||
#### Save requests
|
||||
|
||||
```cs
|
||||
// Token: 0x06001CF6 RID: 7414 RVA: 0x0019CAC8 File Offset: 0x0019ACC8
|
||||
[postfix]
|
||||
Class182.method_2()
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
var path = (System.IO.Directory.GetCurrentDirectory() + "\\HTTP_DATA\\").Replace("\\\\", "\\");
|
||||
var file = uri.LocalPath.Replace('/', '.').Remove(0, 1);
|
||||
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
|
||||
if (System.IO.Directory.CreateDirectory(path).Exists && obj != null)
|
||||
{
|
||||
System.IO.File.WriteAllText($@"{path}req.{file}_{time}.json", text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Save responses
|
||||
|
||||
```cs
|
||||
// Token: 0x06001D01 RID: 7425 RVA: 0x0019D200 File Offset: 0x0019B400
|
||||
[postfix]
|
||||
Class182.method_8()
|
||||
{
|
||||
// add this at the end, before "return text3;"
|
||||
// in case you turn this into a harmony patch, text3 = __result
|
||||
var uri = new Uri(url);
|
||||
var path = (System.IO.Directory.GetCurrentDirectory() + "\\HTTP_DATA\\").Replace("\\\\", "\\");
|
||||
var file = uri.LocalPath.Replace('/', '.').Remove(0, 1);
|
||||
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
|
||||
if (System.IO.Directory.CreateDirectory(path).Exists)
|
||||
{
|
||||
System.IO.File.WriteAllText($@"{path}resp.{file}_{time}.json", text3);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Disable SSL certification
|
||||
|
||||
```cs
|
||||
// Token: 0x0600509D RID: 20637 RVA: 0x0027D244 File Offset: 0x0027B444
|
||||
[prefix]
|
||||
Class537.ValidateCertificate()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
```cs
|
||||
// Token: 0x0600509E RID: 20638 RVA: 0x0027D2B4 File Offset: 0x0027B4B4
|
||||
[prefix]
|
||||
Class537.ValidateCertificate()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
#### Battleye
|
||||
|
||||
```cs
|
||||
// Token: 0x06006B7A RID: 27514 RVA: 0x002D55B8 File Offset: 0x002D37B8
|
||||
[prefix]
|
||||
Class815.RunValidation()
|
||||
{
|
||||
this.Succeed = true;
|
||||
}
|
||||
```
|
||||
|
||||
### FilesChecker.dll
|
||||
|
||||
#### Consistency multi
|
||||
|
||||
```cs
|
||||
// Token: 0x06000054 RID: 84 RVA: 0x00002A38 File Offset: 0x00000C38
|
||||
[prefix]
|
||||
ConsistencyController.EnsureConsistency()
|
||||
{
|
||||
return Task.FromResult<ICheckResult>(ConsistencyController.CheckResult.Succeed(new TimeSpan()));
|
||||
}
|
||||
```
|
||||
|
||||
#### Consistency single
|
||||
|
||||
```cs
|
||||
// Token: 0x06000053 RID: 83 RVA: 0x000028D4 File Offset: 0x00000AD4
|
||||
[prefix]
|
||||
ConsistencyController.EnsureConsistencySingle()
|
||||
{
|
||||
return Task.FromResult<ICheckResult>(ConsistencyController.CheckResult.Succeed(new TimeSpan()));
|
||||
}
|
||||
```
|
33
project/Aki.Build/Aki.Build.csproj
Normal file
33
project/Aki.Build/Aki.Build.csproj
Normal file
@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Company>Aki</Company>
|
||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Build.ps1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||
<ProjectReference Include="..\Aki.Core\Aki.Core.csproj" />
|
||||
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||
<ProjectReference Include="..\Aki.SinglePlayer\Aki.SinglePlayer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="dotnet cake "../build.cake" --vsbuilt=true" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
24
project/Aki.Common/Aki.Common.csproj
Normal file
24
project/Aki.Common/Aki.Common.csproj
Normal file
@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Company>Aki</Company>
|
||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="bsg.componentace.compression.libs.zlib" HintPath="..\Shared\Managed\bsg.componentace.compression.libs.zlib.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BepInEx.Core" Version="5.4.21" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
83
project/Aki.Common/Http/Request.cs
Normal file
83
project/Aki.Common/Http/Request.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Aki.Common.Utils;
|
||||
|
||||
namespace Aki.Common.Http
|
||||
{
|
||||
public class Request
|
||||
{
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
if (!WebConstants.IsValidMethod(method))
|
||||
{
|
||||
throw new ArgumentException("request method is invalid");
|
||||
}
|
||||
|
||||
Uri uri = new Uri(url);
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
|
||||
|
||||
if (uri.Scheme == "https")
|
||||
{
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||
request.ServerCertificateValidationCallback = delegate { return true; };
|
||||
}
|
||||
|
||||
request.Timeout = 15000;
|
||||
request.Method = method;
|
||||
request.Headers.Add("Accept-Encoding", "deflate");
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> item in headers)
|
||||
{
|
||||
request.Headers.Add(item.Key, item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (method != WebConstants.Get && method != WebConstants.Head && data != null)
|
||||
{
|
||||
byte[] body = (compress) ? Zlib.Compress(data, ZlibCompression.Maximum) : data;
|
||||
|
||||
request.ContentType = WebConstants.IsValidMime(mime) ? mime : "application/octet-stream";
|
||||
request.ContentLength = body.Length;
|
||||
|
||||
if (compress)
|
||||
{
|
||||
request.Headers.Add("Content-Encoding", "deflate");
|
||||
}
|
||||
|
||||
using (Stream stream = request.GetRequestStream())
|
||||
{
|
||||
stream.Write(body, 0, body.Length);
|
||||
}
|
||||
}
|
||||
|
||||
using (WebResponse response = request.GetResponse())
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
response.GetResponseStream().CopyTo(ms);
|
||||
byte[] body = ms.ToArray();
|
||||
|
||||
if (body.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Zlib.IsCompressed(body))
|
||||
{
|
||||
return Zlib.Decompress(body);
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
111
project/Aki.Common/Http/RequestHandler.cs
Normal file
111
project/Aki.Common/Http/RequestHandler.cs
Normal file
@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Aki.Common.Utils;
|
||||
using BepInEx.Logging;
|
||||
|
||||
namespace Aki.Common.Http
|
||||
{
|
||||
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;
|
||||
|
||||
static RequestHandler()
|
||||
{
|
||||
_logger = Logger.CreateLogSource(nameof(RequestHandler));
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private static void Initialize()
|
||||
{
|
||||
_request = new Request();
|
||||
|
||||
string[] args = Environment.GetCommandLineArgs();
|
||||
|
||||
foreach (string arg in args)
|
||||
{
|
||||
if (arg.Contains("BackendUrl"))
|
||||
{
|
||||
string json = arg.Replace("-config=", string.Empty);
|
||||
_host = Json.Deserialize<ServerConfig>(json).BackendUrl;
|
||||
}
|
||||
|
||||
if (arg.Contains("-token="))
|
||||
{
|
||||
_session = arg.Replace("-token=", string.Empty);
|
||||
_headers = new Dictionary<string, string>()
|
||||
{
|
||||
{ "Cookie", $"PHPSESSID={_session}" },
|
||||
{ "SessionId", _session }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateData(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
_logger.LogError($"Request failed, body is null");
|
||||
}
|
||||
|
||||
_logger.LogInfo($"Request was successful");
|
||||
}
|
||||
|
||||
private static void ValidateJson(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
_logger.LogError($"Request failed, body is null");
|
||||
}
|
||||
|
||||
_logger.LogInfo($"Request was successful");
|
||||
}
|
||||
|
||||
public static byte[] GetData(string path, bool hasHost = false)
|
||||
{
|
||||
string url = (hasHost) ? path : _host + path;
|
||||
|
||||
_logger.LogInfo($"Request GET data: {_session}:{url}");
|
||||
byte[] result = _request.Send(url, "GET", null, headers: _headers);
|
||||
|
||||
ValidateData(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetJson(string path, bool hasHost = false)
|
||||
{
|
||||
string url = (hasHost) ? path : _host + path;
|
||||
|
||||
_logger.LogInfo($"Request GET json: {_session}:{url}");
|
||||
byte[] data = _request.Send(url, "GET", headers: _headers);
|
||||
string result = Encoding.UTF8.GetString(data);
|
||||
|
||||
ValidateJson(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string PostJson(string path, string json, bool hasHost = false)
|
||||
{
|
||||
string url = (hasHost) ? path : _host + path;
|
||||
|
||||
_logger.LogInfo($"Request POST json: {_session}:{url}");
|
||||
byte[] data = _request.Send(url, "POST", Encoding.UTF8.GetBytes(json), true, "application/json", _headers);
|
||||
string result = Encoding.UTF8.GetString(data);
|
||||
|
||||
ValidateJson(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void PutJson(string path, string json, bool hasHost = false)
|
||||
{
|
||||
string url = (hasHost) ? path : _host + path;
|
||||
_logger.LogInfo($"Request PUT json: {_session}:{url}");
|
||||
_request.Send(url, "PUT", Encoding.UTF8.GetBytes(json), true, "application/json", _headers);
|
||||
}
|
||||
}
|
||||
}
|
14
project/Aki.Common/Http/ServerConfig.cs
Normal file
14
project/Aki.Common/Http/ServerConfig.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Aki.Common.Http
|
||||
{
|
||||
public class ServerConfig
|
||||
{
|
||||
public string BackendUrl { get; }
|
||||
public string Version { get; }
|
||||
|
||||
public ServerConfig(string backendUrl, string version)
|
||||
{
|
||||
BackendUrl = backendUrl;
|
||||
Version = version;
|
||||
}
|
||||
}
|
||||
}
|
94
project/Aki.Common/Http/WebConstants.cs
Normal file
94
project/Aki.Common/Http/WebConstants.cs
Normal file
@ -0,0 +1,94 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Aki.Common.Http
|
||||
{
|
||||
public static class WebConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// HTML GET method.
|
||||
/// </summary>
|
||||
public const string Get = "GET";
|
||||
|
||||
/// <summary>
|
||||
/// HTML HEAD method.
|
||||
/// </summary>
|
||||
public const string Head = "HEAD";
|
||||
|
||||
/// <summary>
|
||||
/// HTML POST method.
|
||||
/// </summary>
|
||||
public const string Post = "POST";
|
||||
|
||||
/// <summary>
|
||||
/// HTML PUT method.
|
||||
/// </summary>
|
||||
public const string Put = "PUT";
|
||||
|
||||
/// <summary>
|
||||
/// HTML DELETE method.
|
||||
/// </summary>
|
||||
public const string Delete = "DELETE";
|
||||
|
||||
/// <summary>
|
||||
/// HTML CONNECT method.
|
||||
/// </summary>
|
||||
public const string Connect = "CONNECT";
|
||||
|
||||
/// <summary>
|
||||
/// HTML OPTIONS method.
|
||||
/// </summary>
|
||||
public const string Options = "OPTIONS";
|
||||
|
||||
/// <summary>
|
||||
/// HTML TRACE method.
|
||||
/// </summary>
|
||||
public const string Trace = "TRACE";
|
||||
|
||||
/// <summary>
|
||||
/// HTML MIME types.
|
||||
/// </summary>
|
||||
public static Dictionary<string, string> Mime { get; private set; }
|
||||
|
||||
static WebConstants()
|
||||
{
|
||||
Mime = new Dictionary<string, string>()
|
||||
{
|
||||
{ ".bin", "application/octet-stream" },
|
||||
{ ".txt", "text/plain" },
|
||||
{ ".htm", "text/html" },
|
||||
{ ".html", "text/html" },
|
||||
{ ".css", "text/css" },
|
||||
{ ".js", "text/javascript" },
|
||||
{ ".jpeg", "image/jpeg" },
|
||||
{ ".jpg", "image/jpeg" },
|
||||
{ ".png", "image/png" },
|
||||
{ ".ico", "image/vnd.microsoft.icon" },
|
||||
{ ".json", "application/json" }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is HTML method valid?
|
||||
/// </summary>
|
||||
public static bool IsValidMethod(string method)
|
||||
{
|
||||
return method == Get
|
||||
|| method == Head
|
||||
|| method == Post
|
||||
|| method == Put
|
||||
|| method == Delete
|
||||
|| method == Connect
|
||||
|| method == Options
|
||||
|| method == Trace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is MIME type valid?
|
||||
/// </summary>
|
||||
public static bool IsValidMime(string mime)
|
||||
{
|
||||
return Mime.Any(x => x.Value == mime);
|
||||
}
|
||||
}
|
||||
}
|
17
project/Aki.Common/Utils/Json.cs
Normal file
17
project/Aki.Common/Utils/Json.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Aki.Common.Utils
|
||||
{
|
||||
public static class Json
|
||||
{
|
||||
public static string Serialize<T>(T data)
|
||||
{
|
||||
return JsonConvert.SerializeObject(data);
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(string json)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json);
|
||||
}
|
||||
}
|
||||
}
|
213
project/Aki.Common/Utils/VFS.cs
Normal file
213
project/Aki.Common/Utils/VFS.cs
Normal file
@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Aki.Common.Utils
|
||||
{
|
||||
public static class VFS
|
||||
{
|
||||
public static string Cwd { get; private set; }
|
||||
|
||||
static VFS()
|
||||
{
|
||||
Cwd = Environment.CurrentDirectory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combine two filepaths.
|
||||
/// </summary>
|
||||
public static string Combine(string path1, string path2)
|
||||
{
|
||||
return Path.Combine(path1, path2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines the filepath with the current working directory.
|
||||
/// </summary>
|
||||
public static string FromCwd(this string filepath)
|
||||
{
|
||||
return Combine(Cwd, filepath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get directory path of a filepath.
|
||||
/// </summary>
|
||||
public static string GetDirectory(this string filepath)
|
||||
{
|
||||
string value = Path.GetDirectoryName(filepath);
|
||||
return (!string.IsNullOrWhiteSpace(value)) ? value : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file of a filepath
|
||||
/// </summary>
|
||||
public static string GetFile(this string filepath)
|
||||
{
|
||||
string value = Path.GetFileName(filepath);
|
||||
return (!string.IsNullOrWhiteSpace(value)) ? value : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file name of a filepath
|
||||
/// </summary>
|
||||
public static string GetFileName(this string filepath)
|
||||
{
|
||||
string value = Path.GetFileNameWithoutExtension(filepath);
|
||||
return (!string.IsNullOrWhiteSpace(value)) ? value : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file extension of a filepath.
|
||||
/// </summary>
|
||||
public static string GetFileExtension(this string filepath)
|
||||
{
|
||||
string value = Path.GetExtension(filepath);
|
||||
return (!string.IsNullOrWhiteSpace(value)) ? value : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move file from one place to another
|
||||
/// </summary>
|
||||
public static void MoveFile(string a, string b)
|
||||
{
|
||||
new FileInfo(a).MoveTo(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the filepath exist?
|
||||
/// </summary>
|
||||
public static bool Exists(string filepath)
|
||||
{
|
||||
return Directory.Exists(filepath) || File.Exists(filepath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create directory (recursive).
|
||||
/// </summary>
|
||||
public static void CreateDirectory(string filepath)
|
||||
{
|
||||
Directory.CreateDirectory(filepath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file content as bytes.
|
||||
/// </summary>
|
||||
public static byte[] ReadFile(string filepath)
|
||||
{
|
||||
return File.ReadAllBytes(filepath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file content as string.
|
||||
/// </summary>
|
||||
public static string ReadTextFile(string filepath)
|
||||
{
|
||||
return File.ReadAllText(filepath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write data to file.
|
||||
/// </summary>
|
||||
public static void WriteFile(string filepath, byte[] data)
|
||||
{
|
||||
if (!Exists(filepath))
|
||||
{
|
||||
CreateDirectory(filepath.GetDirectory());
|
||||
}
|
||||
|
||||
File.WriteAllBytes(filepath, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write string to file.
|
||||
/// </summary>
|
||||
public static void WriteTextFile(string filepath, string data, bool append = false)
|
||||
{
|
||||
if (!Exists(filepath))
|
||||
{
|
||||
CreateDirectory(filepath.GetDirectory());
|
||||
}
|
||||
|
||||
if (append)
|
||||
{
|
||||
File.AppendAllText(filepath, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.WriteAllText(filepath, data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get directories in directory by full path.
|
||||
/// </summary>
|
||||
public static string[] GetDirectories(string filepath)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||
List<string> paths = new List<string>();
|
||||
|
||||
foreach (DirectoryInfo directory in di.GetDirectories())
|
||||
{
|
||||
paths.Add(directory.FullName);
|
||||
}
|
||||
|
||||
return paths.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get files in directory by full path.
|
||||
/// </summary>
|
||||
public static string[] GetFiles(string filepath)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||
List<string> paths = new List<string>();
|
||||
|
||||
foreach (FileInfo file in di.GetFiles())
|
||||
{
|
||||
paths.Add(file.FullName);
|
||||
}
|
||||
|
||||
return paths.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete directory.
|
||||
/// </summary>
|
||||
public static void DeleteDirectory(string filepath)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||
|
||||
foreach (FileInfo file in di.GetFiles())
|
||||
{
|
||||
file.IsReadOnly = false;
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo directory in di.GetDirectories())
|
||||
{
|
||||
DeleteDirectory(directory.FullName);
|
||||
}
|
||||
|
||||
di.Delete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete file.
|
||||
/// </summary>
|
||||
public static void DeleteFile(string filepath)
|
||||
{
|
||||
FileInfo file = new FileInfo(filepath);
|
||||
file.IsReadOnly = false;
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get files count inside directory recursively
|
||||
/// </summary>
|
||||
public static int GetFilesCount(string filepath)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(filepath);
|
||||
return di.Exists ? di.GetFiles("*.*", SearchOption.AllDirectories).Length : -1;
|
||||
}
|
||||
}
|
||||
}
|
127
project/Aki.Common/Utils/Zlib.cs
Normal file
127
project/Aki.Common/Utils/Zlib.cs
Normal file
@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using ComponentAce.Compression.Libs.zlib;
|
||||
|
||||
namespace Aki.Common.Utils
|
||||
{
|
||||
public enum ZlibCompression
|
||||
{
|
||||
Store = 0,
|
||||
Fastest = 1,
|
||||
Fast = 3,
|
||||
Normal = 5,
|
||||
Ultra = 7,
|
||||
Maximum = 9
|
||||
}
|
||||
|
||||
public static class Zlib
|
||||
{
|
||||
// Level | CM/CI FLG
|
||||
// ----- | ---------
|
||||
// 1 | 78 01
|
||||
// 2 | 78 5E
|
||||
// 3 | 78 5E
|
||||
// 4 | 78 5E
|
||||
// 5 | 78 5E
|
||||
// 6 | 78 9C
|
||||
// 7 | 78 DA
|
||||
// 8 | 78 DA
|
||||
// 9 | 78 DA
|
||||
|
||||
/// <summary>
|
||||
/// Check if the file is ZLib compressed
|
||||
/// </summary>
|
||||
/// <param name="Data">Data</param>
|
||||
/// <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)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (Data[1])
|
||||
{
|
||||
case 0x01: // fastest
|
||||
case 0x5E: // low
|
||||
case 0x9C: // normal
|
||||
case 0xDA: // max
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deflate data.
|
||||
/// </summary>
|
||||
public static byte[] Compress(byte[] data, ZlibCompression level)
|
||||
{
|
||||
byte[] buffer = new byte[data.Length + 24];
|
||||
|
||||
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.deflateInit((int)level);
|
||||
zs.deflate(zlibConst.Z_FINISH);
|
||||
|
||||
data = new byte[zs.next_out_index];
|
||||
Array.Copy(zs.next_out, 0, data, 0, zs.next_out_index);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inflate data.
|
||||
/// </summary>
|
||||
public static byte[] Decompress(byte[] data)
|
||||
{
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
project/Aki.Core/Aki.Core.csproj
Normal file
35
project/Aki.Core/Aki.Core.csproj
Normal file
@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AssemblyName>aki-core</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Company>Aki</Company>
|
||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||
<Reference Include="Assembly-CSharp-firstpass" HintPath="..\Shared\Managed\Assembly-CSharp-firstpass.dll" Private="False" />
|
||||
<Reference Include="FilesChecker" HintPath="..\Shared\Managed\FilesChecker.dll" Private="False" />
|
||||
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||
<Reference Include="UnityEngine.UnityWebRequestModule" HintPath="..\Shared\Managed\UnityEngine.UnityWebRequestModule.dll" Private="False" />
|
||||
<Reference Include="UnityEngine.UnityWebRequestTextureModule" HintPath="..\Shared\Managed\UnityEngine.UnityWebRequestTextureModule.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
33
project/Aki.Core/AkiCorePlugin.cs
Normal file
33
project/Aki.Core/AkiCorePlugin.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using Aki.Core.Patches;
|
||||
using BepInEx;
|
||||
|
||||
namespace Aki.Core
|
||||
{
|
||||
[BepInPlugin("com.spt-aki.core", "AKI.Core", "1.0.0")]
|
||||
class AkiCorePlugin : BaseUnityPlugin
|
||||
{
|
||||
public AkiCorePlugin()
|
||||
{
|
||||
Logger.LogInfo("Loading: Aki.Core");
|
||||
|
||||
try
|
||||
{
|
||||
new ConsistencySinglePatch().Enable();
|
||||
new ConsistencyMultiPatch().Enable();
|
||||
new BattlEyePatch().Enable();
|
||||
new SslCertificatePatch().Enable();
|
||||
new UnityWebRequestPatch().Enable();
|
||||
new WebSocketPatch().Enable();
|
||||
new TransportPrefixPatch().Enable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"{GetType().Name}: {ex}");
|
||||
throw;
|
||||
}
|
||||
|
||||
Logger.LogInfo("Completed: Aki.Core");
|
||||
}
|
||||
}
|
||||
}
|
13
project/Aki.Core/Models/FakeCertificateHandler.cs
Normal file
13
project/Aki.Core/Models/FakeCertificateHandler.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using UnityEngine.Networking;
|
||||
using Aki.Core.Utils;
|
||||
|
||||
namespace Aki.Core.Models
|
||||
{
|
||||
public class FakeCertificateHandler : CertificateHandler
|
||||
{
|
||||
protected override bool ValidateCertificate(byte[] certificateData)
|
||||
{
|
||||
return ValidationUtil.Validate();
|
||||
}
|
||||
}
|
||||
}
|
17
project/Aki.Core/Models/FakeFileCheckerResult.cs
Normal file
17
project/Aki.Core/Models/FakeFileCheckerResult.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using FilesChecker;
|
||||
|
||||
namespace Aki.Core.Models
|
||||
{
|
||||
public class FakeFileCheckerResult : ICheckResult
|
||||
{
|
||||
public TimeSpan ElapsedTime { get; private set; }
|
||||
public Exception Exception { get; private set; }
|
||||
|
||||
public FakeFileCheckerResult()
|
||||
{
|
||||
ElapsedTime = new TimeSpan();
|
||||
Exception = null;
|
||||
}
|
||||
}
|
||||
}
|
29
project/Aki.Core/Patches/BattlEyePatch.cs
Normal file
29
project/Aki.Core/Patches/BattlEyePatch.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Aki.Core.Utils;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aki.Core.Patches
|
||||
{
|
||||
public class BattlEyePatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var methodName = "RunValidation";
|
||||
var flags = BindingFlags.Public | BindingFlags.Instance;
|
||||
|
||||
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
|
||||
.GetMethod(methodName, flags);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref Task __result, ref bool ___bool_0)
|
||||
{
|
||||
___bool_0 = ValidationUtil.Validate();
|
||||
__result = Task.CompletedTask;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
26
project/Aki.Core/Patches/ConsistencyMultiPatch.cs
Normal file
26
project/Aki.Core/Patches/ConsistencyMultiPatch.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Aki.Core.Models;
|
||||
using FilesChecker;
|
||||
|
||||
namespace Aki.Core.Patches
|
||||
{
|
||||
public class ConsistencyMultiPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return PatchConstants.FilesCheckerTypes.Single(x => x.Name == "ConsistencyController")
|
||||
.GetMethods().Single(x => x.Name == "EnsureConsistency" && x.ReturnType == typeof(Task<ICheckResult>));
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref object __result)
|
||||
{
|
||||
__result = Task.FromResult<ICheckResult>(new FakeFileCheckerResult());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
26
project/Aki.Core/Patches/ConsistencySinglePatch.cs
Normal file
26
project/Aki.Core/Patches/ConsistencySinglePatch.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Aki.Core.Models;
|
||||
using FilesChecker;
|
||||
|
||||
namespace Aki.Core.Patches
|
||||
{
|
||||
public class ConsistencySinglePatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return PatchConstants.FilesCheckerTypes.Single(x => x.Name == "ConsistencyController")
|
||||
.GetMethods().Single(x => x.Name == "EnsureConsistencySingle" && x.ReturnType == typeof(Task<ICheckResult>));
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref object __result)
|
||||
{
|
||||
__result = Task.FromResult<ICheckResult>(new FakeFileCheckerResult());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
30
project/Aki.Core/Patches/DataHandlerDebugPatch.cs
Normal file
30
project/Aki.Core/Patches/DataHandlerDebugPatch.cs
Normal file
@ -0,0 +1,30 @@
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
25
project/Aki.Core/Patches/SslCertificatePatch.cs
Normal file
25
project/Aki.Core/Patches/SslCertificatePatch.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine.Networking;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Aki.Core.Utils;
|
||||
|
||||
namespace Aki.Core.Patches
|
||||
{
|
||||
public class SslCertificatePatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return PatchConstants.EftTypes.Single(x => x.BaseType == typeof(CertificateHandler))
|
||||
.GetMethod("ValidateCertificate", PatchConstants.PrivateFlags);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref bool __result)
|
||||
{
|
||||
__result = ValidationUtil.Validate();
|
||||
return false; // Skip origial
|
||||
}
|
||||
}
|
||||
}
|
75
project/Aki.Core/Patches/TransportPrefixPatch.cs
Normal file
75
project/Aki.Core/Patches/TransportPrefixPatch.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Aki.Core.Patches
|
||||
{
|
||||
public class TransportPrefixPatch : ModulePatch
|
||||
{
|
||||
public TransportPrefixPatch()
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = PatchConstants.EftTypes.Single(t => t.Name == "Class228");
|
||||
var value = Traverse.Create(type).Field("TransportPrefixes").GetValue<Dictionary<ETransportProtocolType, string>>();
|
||||
value[ETransportProtocolType.HTTPS] = "http://";
|
||||
value[ETransportProtocolType.WSS] = "ws://";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"{nameof(TransportPrefixPatch)}: {ex}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return PatchConstants.EftTypes.Single(t => t.GetMethods().Any(m => m.Name == "CreateFromLegacyParams"))
|
||||
.GetMethod("CreateFromLegacyParams", BindingFlags.Static | BindingFlags.Public);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref GStruct22 legacyParams)
|
||||
{
|
||||
//Console.WriteLine($"Original url {legacyParams.Url}");
|
||||
legacyParams.Url = legacyParams.Url
|
||||
.Replace("https://", "")
|
||||
.Replace("http://", "");
|
||||
//Console.WriteLine($"Edited url {legacyParams.Url}");
|
||||
return true; // do original method after
|
||||
}
|
||||
|
||||
[PatchTranspiler]
|
||||
private static IEnumerable<CodeInstruction> PatchTranspile(ILGenerator generator, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
var codes = new List<CodeInstruction>(instructions);
|
||||
|
||||
var searchCode = new CodeInstruction(OpCodes.Ldstr, "https://");
|
||||
var searchIndex = -1;
|
||||
|
||||
for (var i = 0; i < codes.Count; i++)
|
||||
{
|
||||
if (codes[i].opcode == searchCode.opcode && codes[i].operand == searchCode.operand)
|
||||
{
|
||||
searchIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (searchIndex == -1)
|
||||
{
|
||||
Logger.LogError($"{nameof(TransportPrefixPatch)} failed: Could not find reference code.");
|
||||
return instructions;
|
||||
}
|
||||
|
||||
codes[searchIndex] = new CodeInstruction(OpCodes.Ldstr, "http://");
|
||||
|
||||
return codes.AsEnumerable();
|
||||
}
|
||||
}
|
||||
}
|
23
project/Aki.Core/Patches/UnityWebRequestPatch.cs
Normal file
23
project/Aki.Core/Patches/UnityWebRequestPatch.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.Reflection;
|
||||
using UnityEngine.Networking;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Core.Models;
|
||||
|
||||
namespace Aki.Core.Patches
|
||||
{
|
||||
public class UnityWebRequestPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(UnityWebRequestTexture).GetMethod(nameof(UnityWebRequestTexture.GetTexture), new[] { typeof(string) });
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix(UnityWebRequest __result)
|
||||
{
|
||||
__result.certificateHandler = new FakeCertificateHandler();
|
||||
__result.disposeCertificateHandlerOnDispose = true;
|
||||
__result.timeout = 15000;
|
||||
}
|
||||
}
|
||||
}
|
24
project/Aki.Core/Patches/WebSocketPatch.cs
Normal file
24
project/Aki.Core/Patches/WebSocketPatch.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Core.Patches
|
||||
{
|
||||
public class WebSocketPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var targetInterface = PatchConstants.EftTypes.Single(x => x == typeof(IConnectionHandler) && x.IsInterface);
|
||||
var typeThatMatches = PatchConstants.EftTypes.Single(x => targetInterface.IsAssignableFrom(x) && x.IsAbstract && !x.IsInterface);
|
||||
return typeThatMatches.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Single(x => x.ReturnType == typeof(Uri));
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static Uri PatchPostfix(Uri __instance)
|
||||
{
|
||||
return new Uri(__instance.ToString().Replace("wss:", "ws:"));
|
||||
}
|
||||
}
|
||||
}
|
46
project/Aki.Core/Utils/ValidationUtil.cs
Normal file
46
project/Aki.Core/Utils/ValidationUtil.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Microsoft.Win32;
|
||||
using System.IO;
|
||||
|
||||
namespace Aki.Core.Utils
|
||||
{
|
||||
public static class ValidationUtil
|
||||
{
|
||||
public static bool Validate()
|
||||
{
|
||||
var c0 = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov";
|
||||
var v0 = 0;
|
||||
|
||||
try
|
||||
{
|
||||
var v1 = Registry.LocalMachine.OpenSubKey(c0, false).GetValue("UninstallString");
|
||||
var v2 = (v1 != null) ? v1.ToString() : string.Empty;
|
||||
var v3 = new FileInfo(v2);
|
||||
var v4 = new FileInfo[]
|
||||
{
|
||||
v3,
|
||||
new FileInfo(v2.Replace(v3.Name, @"BattlEye\BEClient_x64.dll")),
|
||||
new FileInfo(v2.Replace(v3.Name, @"BattlEye\BEService_x64.dll")),
|
||||
new FileInfo(v2.Replace(v3.Name, @"ConsistencyInfo")),
|
||||
new FileInfo(v2.Replace(v3.Name, @"Uninstall.exe")),
|
||||
new FileInfo(v2.Replace(v3.Name, @"UnityCrashHandler64.exe"))
|
||||
};
|
||||
|
||||
v0 = v4.Length - 1;
|
||||
|
||||
foreach (var value in v4)
|
||||
{
|
||||
if (File.Exists(value.FullName))
|
||||
{
|
||||
--v0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
v0 = -1;
|
||||
}
|
||||
|
||||
return v0 == 0;
|
||||
}
|
||||
}
|
||||
}
|
241
project/Aki.Custom/Airdrops/AirdropBox.cs
Normal file
241
project/Aki.Custom/Airdrops/AirdropBox.cs
Normal file
@ -0,0 +1,241 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Comfort.Common;
|
||||
using EFT.Airdrop;
|
||||
using EFT.Interactive;
|
||||
using EFT.SynchronizableObjects;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Aki.Custom.Airdrops
|
||||
{
|
||||
public class AirdropBox : MonoBehaviour
|
||||
{
|
||||
private const string CRATE_PATH = "assets/content/location_objects/lootable/prefab/scontainer_crate.bundle";
|
||||
private const string AIRDROP_SOUNDS_PATH = "assets/content/audio/prefabs/airdrop/airdropsounds.bundle";
|
||||
private readonly int CROSSFADE = Shader.PropertyToID("_Crossfade");
|
||||
private readonly int COLLISION = Animator.StringToHash("collision");
|
||||
|
||||
public LootableContainer container;
|
||||
private float fallSpeed;
|
||||
private AirdropSynchronizableObject boxSync;
|
||||
private AirdropLogicClass boxLogic;
|
||||
private Material paraMaterial;
|
||||
private Animator paraAnimator;
|
||||
private AirdropSurfaceSet surfaceSet;
|
||||
private Dictionary<BaseBallistic.ESurfaceSound, AirdropSurfaceSet> soundsDictionary;
|
||||
private BetterSource audioSource;
|
||||
|
||||
private BetterSource AudioSource
|
||||
{
|
||||
get
|
||||
{
|
||||
if (audioSource != null) return audioSource;
|
||||
|
||||
audioSource = Singleton<BetterAudio>.Instance.GetSource(BetterAudio.AudioSourceGroupType.Environment, false);
|
||||
audioSource.transform.parent = transform;
|
||||
audioSource.transform.localPosition = Vector3.up;
|
||||
|
||||
return audioSource;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<AirdropBox> Init(float crateFallSpeed)
|
||||
{
|
||||
var instance = (await LoadCrate()).AddComponent<AirdropBox>();
|
||||
instance.soundsDictionary = await LoadSounds();
|
||||
|
||||
instance.container = instance.GetComponentInChildren<LootableContainer>();
|
||||
|
||||
instance.boxSync = instance.GetComponent<AirdropSynchronizableObject>();
|
||||
instance.boxLogic = new AirdropLogicClass();
|
||||
instance.boxSync.SetLogic(instance.boxLogic);
|
||||
|
||||
instance.paraAnimator = instance.boxSync.Parachute.GetComponent<Animator>();
|
||||
instance.paraMaterial = instance.boxSync.Parachute.GetComponentInChildren<Renderer>().material;
|
||||
instance.fallSpeed = crateFallSpeed;
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static async Task<GameObject> LoadCrate()
|
||||
{
|
||||
var easyAssets = Singleton<PoolManager>.Instance.EasyAssets;
|
||||
await easyAssets.Retain(CRATE_PATH, null, null).LoadingJob;
|
||||
|
||||
var crate = Instantiate(easyAssets.GetAsset<GameObject>(CRATE_PATH));
|
||||
crate.SetActive(false);
|
||||
return crate;
|
||||
}
|
||||
|
||||
private static async Task<Dictionary<BaseBallistic.ESurfaceSound, AirdropSurfaceSet>> LoadSounds()
|
||||
{
|
||||
var easyAssets = Singleton<PoolManager>.Instance.EasyAssets;
|
||||
await easyAssets.Retain(AIRDROP_SOUNDS_PATH, null, null).LoadingJob;
|
||||
|
||||
var soundsDictionary = new Dictionary<BaseBallistic.ESurfaceSound, AirdropSurfaceSet>();
|
||||
var sets = easyAssets.GetAsset<AirdropSounds>(AIRDROP_SOUNDS_PATH).Sets;
|
||||
foreach (var set in sets)
|
||||
{
|
||||
if (!soundsDictionary.ContainsKey(set.Surface))
|
||||
{
|
||||
soundsDictionary.Add(set.Surface, set);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(set.Surface + " surface sounds are duplicated");
|
||||
}
|
||||
}
|
||||
|
||||
return soundsDictionary;
|
||||
}
|
||||
|
||||
public IEnumerator DropCrate(Vector3 position)
|
||||
{
|
||||
RaycastBoxDistance(LayerMaskClass.TerrainLowPoly, out var hitInfo, position);
|
||||
SetLandingSound();
|
||||
boxSync.Init(1, position, Vector3.zero);
|
||||
PlayAudioClip(boxSync.SqueakClip, true);
|
||||
|
||||
if(hitInfo.distance < 155f)
|
||||
{
|
||||
for (float i = 0; i < 1; i += Time.deltaTime / 6f)
|
||||
{
|
||||
transform.position = Vector3.Lerp(position, hitInfo.point, i*i);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
transform.position = hitInfo.point;
|
||||
}
|
||||
else
|
||||
{
|
||||
var parachuteOpenPos = position + new Vector3(0f, -148.2f, 0f); // (5.5s * -9.8m/s^2) / 2
|
||||
for (float i = 0; i < 1; i += Time.deltaTime / 5.5f)
|
||||
{
|
||||
transform.position = Vector3.Lerp(position, parachuteOpenPos, i * i);
|
||||
yield return null;
|
||||
}
|
||||
OpenParachute();
|
||||
while (RaycastBoxDistance(LayerMaskClass.TerrainLowPoly, out _))
|
||||
{
|
||||
transform.Translate(Vector3.down * (Time.deltaTime * fallSpeed));
|
||||
transform.Rotate(Vector3.up, Time.deltaTime * 6f);
|
||||
yield return null;
|
||||
}
|
||||
transform.position = hitInfo.point;
|
||||
CloseParachute();
|
||||
}
|
||||
|
||||
OnBoxLand(out var clipLength);
|
||||
yield return new WaitForSecondsRealtime(clipLength + 0.5f);
|
||||
ReleaseAudioSource();
|
||||
}
|
||||
|
||||
private void OnBoxLand(out float clipLength)
|
||||
{
|
||||
var landingClip = surfaceSet.LandingSoundBank.PickSingleClip(surfaceSet.LandingSoundBank.GetRandomClipIndex(2));
|
||||
clipLength = landingClip.length;
|
||||
boxSync.AirdropDust.SetActive(true);
|
||||
boxSync.AirdropDust.GetComponent<ParticleSystem>().Play();
|
||||
AudioSource.source1.Stop();
|
||||
PlayAudioClip(new TaggedClip
|
||||
{
|
||||
Clip = landingClip,
|
||||
Falloff = (int)surfaceSet.LandingSoundBank.Rolloff,
|
||||
Volume = surfaceSet.LandingSoundBank.BaseVolume
|
||||
});
|
||||
}
|
||||
|
||||
private bool RaycastBoxDistance(LayerMask layerMask, out RaycastHit hitInfo)
|
||||
{
|
||||
return RaycastBoxDistance(layerMask, out hitInfo, transform.position);
|
||||
}
|
||||
|
||||
private bool RaycastBoxDistance(LayerMask layerMask, out RaycastHit hitInfo, Vector3 origin)
|
||||
{
|
||||
var ray = new Ray(origin, Vector3.down);
|
||||
|
||||
var raycast = Physics.Raycast(ray, out hitInfo, Mathf.Infinity, layerMask);
|
||||
if (!raycast) return false;
|
||||
|
||||
return hitInfo.distance > 0.05f;
|
||||
}
|
||||
|
||||
private void SetLandingSound()
|
||||
{
|
||||
if (!RaycastBoxDistance(LayerMaskClass.AudioControllerStepLayerMask, out var raycast))
|
||||
{
|
||||
Debug.LogError("Raycast to ground returns no hit. Choose Concrete sound landing set");
|
||||
surfaceSet = soundsDictionary[BaseBallistic.ESurfaceSound.Concrete];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (raycast.collider.TryGetComponent(out BaseBallistic component))
|
||||
{
|
||||
var surfaceSound = component.GetSurfaceSound(raycast.point);
|
||||
if (soundsDictionary.ContainsKey(surfaceSound))
|
||||
{
|
||||
surfaceSet = soundsDictionary[surfaceSound];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
surfaceSet = soundsDictionary[BaseBallistic.ESurfaceSound.Concrete];
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayAudioClip(TaggedClip clip, bool looped = false)
|
||||
{
|
||||
var volume = clip.Volume;
|
||||
var occlusionGroupSimple = Singleton<BetterAudio>.Instance.GetOcclusionGroupSimple(transform.position, ref volume);
|
||||
AudioSource.gameObject.SetActive(true);
|
||||
AudioSource.source1.outputAudioMixerGroup = occlusionGroupSimple;
|
||||
AudioSource.source1.spatialBlend = 1f;
|
||||
AudioSource.SetRolloff(clip.Falloff);
|
||||
AudioSource.source1.volume = volume;
|
||||
|
||||
if (AudioSource.source1.isPlaying) return;
|
||||
|
||||
AudioSource.source1.clip = clip.Clip;
|
||||
AudioSource.source1.loop = looped;
|
||||
AudioSource.source1.Play();
|
||||
}
|
||||
|
||||
private void OpenParachute()
|
||||
{
|
||||
boxSync.Parachute.SetActive(true);
|
||||
paraAnimator.SetBool(COLLISION, false);
|
||||
StartCoroutine(CrossFadeAnimation(1f));
|
||||
}
|
||||
|
||||
private void CloseParachute()
|
||||
{
|
||||
paraAnimator.SetBool(COLLISION, true);
|
||||
StartCoroutine(CrossFadeAnimation(0f));
|
||||
}
|
||||
|
||||
private IEnumerator CrossFadeAnimation(float targetFadeValue)
|
||||
{
|
||||
var curFadeValue = paraMaterial.GetFloat(CROSSFADE);
|
||||
for (float i = 0; i < 1; i += Time.deltaTime / 2f)
|
||||
{
|
||||
paraMaterial.SetFloat(CROSSFADE, Mathf.Lerp(curFadeValue, targetFadeValue, i*i));
|
||||
yield return null;
|
||||
}
|
||||
paraMaterial.SetFloat(CROSSFADE, targetFadeValue);
|
||||
|
||||
if (targetFadeValue == 0f)
|
||||
{
|
||||
boxSync.Parachute.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseAudioSource()
|
||||
{
|
||||
if (audioSource == null) return;
|
||||
|
||||
audioSource.transform.parent = null;
|
||||
audioSource.Release();
|
||||
audioSource = null;
|
||||
}
|
||||
}
|
||||
}
|
138
project/Aki.Custom/Airdrops/AirdropPlane.cs
Normal file
138
project/Aki.Custom/Airdrops/AirdropPlane.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using System.Collections;
|
||||
using System.Threading.Tasks;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using EFT.Airdrop;
|
||||
using EFT.SynchronizableObjects;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Aki.Custom.Airdrops
|
||||
{
|
||||
public class AirdropPlane : MonoBehaviour
|
||||
{
|
||||
private const string PLANE_PATH = "assets/content/location_objects/lootable/prefab/il76md-90.prefab";
|
||||
private const float RADIUS_TO_PICK_RANDOM_POINT = 3000f;
|
||||
|
||||
private AirplaneSynchronizableObject airplaneSync;
|
||||
private float speed;
|
||||
private float distanceToDrop;
|
||||
private float flaresCooldown;
|
||||
private bool flaresDeployed;
|
||||
private bool headingChanged;
|
||||
|
||||
public static async Task<AirdropPlane> Init(Vector3 airdropPoint, int dropHeight, float planeVolume, float speed)
|
||||
{
|
||||
var instance = (await LoadPlane()).AddComponent<AirdropPlane>();
|
||||
|
||||
instance.airplaneSync = instance.GetComponent<AirplaneSynchronizableObject>();
|
||||
instance.airplaneSync.SetLogic(new AirplaneLogicClass());
|
||||
|
||||
instance.SetPosition(dropHeight, airdropPoint);
|
||||
instance.SetAudio(planeVolume);
|
||||
instance.speed = speed;
|
||||
instance.gameObject.SetActive(false);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static async Task<GameObject> LoadPlane()
|
||||
{
|
||||
var easyAssets = Singleton<PoolManager>.Instance.EasyAssets;
|
||||
await easyAssets.Retain(PLANE_PATH, null, null).LoadingJob;
|
||||
var plane = Instantiate(easyAssets.GetAsset<GameObject>(PLANE_PATH));
|
||||
return plane;
|
||||
}
|
||||
|
||||
private void SetAudio(float planeVolume)
|
||||
{
|
||||
var airplaneAudio = gameObject.AddComponent<AudioSource>();
|
||||
airplaneAudio.clip = airplaneSync.soundClip.Clip;
|
||||
|
||||
airplaneAudio.dopplerLevel = 1f;
|
||||
airplaneAudio.outputAudioMixerGroup = Singleton<BetterAudio>.Instance.VeryStandartMixerGroup;
|
||||
airplaneAudio.loop = true;
|
||||
airplaneAudio.maxDistance = 2000;
|
||||
airplaneAudio.minDistance = 1;
|
||||
airplaneAudio.pitch = 0.5f;
|
||||
airplaneAudio.priority = 128;
|
||||
airplaneAudio.reverbZoneMix = 1;
|
||||
airplaneAudio.rolloffMode = AudioRolloffMode.Custom;
|
||||
airplaneAudio.spatialBlend = 1;
|
||||
airplaneAudio.spread = 60;
|
||||
airplaneAudio.volume = planeVolume;
|
||||
|
||||
airplaneAudio.Play();
|
||||
}
|
||||
|
||||
private void SetPosition(int dropHeight, Vector3 airdropPoint)
|
||||
{
|
||||
var pointOnCircle = Random.insideUnitCircle.normalized * RADIUS_TO_PICK_RANDOM_POINT;
|
||||
|
||||
transform.position = new Vector3(pointOnCircle.x, dropHeight, pointOnCircle.y);
|
||||
transform.LookAt(new Vector3(airdropPoint.x, dropHeight, airdropPoint.z));
|
||||
}
|
||||
|
||||
public void ManualUpdate(float distance)
|
||||
{
|
||||
transform.Translate(Vector3.forward * (Time.deltaTime * speed));
|
||||
distanceToDrop = distance;
|
||||
UpdateFlaresLogic();
|
||||
|
||||
if (distance - 200f > 0f || headingChanged) return;
|
||||
|
||||
StartCoroutine(ChangeHeading());
|
||||
headingChanged = true;
|
||||
}
|
||||
|
||||
private void UpdateFlaresLogic()
|
||||
{
|
||||
if (flaresDeployed) return;
|
||||
|
||||
if (distanceToDrop > 0f && flaresCooldown <= Time.unscaledTime)
|
||||
{
|
||||
flaresCooldown = Time.unscaledTime + 4f;
|
||||
StartCoroutine(DeployFlares(Random.Range(0.2f, 0.4f)));
|
||||
}
|
||||
|
||||
if (distanceToDrop > 0f) return;
|
||||
|
||||
flaresDeployed = true;
|
||||
StartCoroutine(DeployFlares(5f));
|
||||
}
|
||||
|
||||
private IEnumerator DeployFlares(float emissionTime)
|
||||
{
|
||||
var projectile = Instantiate(airplaneSync.infraredCountermeasureParticles, transform);
|
||||
projectile.transform.localPosition = new Vector3(0f, -5f, 0f);
|
||||
var flares = projectile.GetComponentsInChildren<ParticleSystem>();
|
||||
var endTime = Time.unscaledTime + emissionTime;
|
||||
Singleton<GameWorld>.Instance.SynchronizableObjectLogicProcessor.AirdropManager.AddProjectile(projectile,
|
||||
endTime + flares[0].main.duration + flares[0].main.startLifetime.Evaluate(1f));
|
||||
|
||||
while (endTime > Time.unscaledTime)
|
||||
yield return null;
|
||||
|
||||
projectile.transform.parent = null;
|
||||
foreach (var particleSystem in flares)
|
||||
particleSystem.Stop();
|
||||
}
|
||||
|
||||
private IEnumerator ChangeHeading()
|
||||
{
|
||||
var startingRotation = transform.eulerAngles;
|
||||
var middleRotation = startingRotation + new Vector3(0f, 40f, -200f);
|
||||
var endRotation = middleRotation + new Vector3(0f, 40f, 200f);
|
||||
|
||||
for (float i = 0; i < 1; i += Time.deltaTime / 25f)
|
||||
{
|
||||
var finalRotation = Vector3.Lerp(middleRotation, endRotation, EasingSmoothSquared(i));
|
||||
transform.eulerAngles = Vector3.Lerp(startingRotation, finalRotation, EasingSmoothSquared(i));
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
private float EasingSmoothSquared(float x)
|
||||
{
|
||||
return x < 0.5 ? x * x * 2 : (1 - (1 - x) * (1 - x) * 2);
|
||||
}
|
||||
}
|
||||
}
|
107
project/Aki.Custom/Airdrops/AirdropsManager.cs
Normal file
107
project/Aki.Custom/Airdrops/AirdropsManager.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using Aki.Custom.Airdrops.Models;
|
||||
using Aki.Custom.Airdrops.Utils;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Aki.Custom.Airdrops
|
||||
{
|
||||
public class AirdropsManager : MonoBehaviour
|
||||
{
|
||||
private AirdropPlane airdropPlane;
|
||||
private AirdropBox airdropBox;
|
||||
private ItemFactoryUtil factory;
|
||||
|
||||
public bool isFlareDrop;
|
||||
private AirdropParametersModel airdropParameters;
|
||||
|
||||
public async void Start()
|
||||
{
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
|
||||
if (gameWorld == null) Destroy(this);
|
||||
|
||||
airdropParameters = AirdropUtil.InitAirdropParams(gameWorld, isFlareDrop);
|
||||
|
||||
if (!airdropParameters.AirdropAvailable)
|
||||
{
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
airdropPlane = await AirdropPlane.Init(airdropParameters.RandomAirdropPoint,
|
||||
airdropParameters.DropHeight, airdropParameters.Config.PlaneVolume, airdropParameters.Config.PlaneSpeed);
|
||||
airdropBox = await AirdropBox.Init(airdropParameters.Config.CrateFallSpeed);
|
||||
factory = new ItemFactoryUtil();
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.LogError($"[AKI-AIRDROPS]: Unable to create plane or crate, airdrop won't occur");
|
||||
Destroy(this);
|
||||
throw;
|
||||
}
|
||||
|
||||
SetDistanceToDrop();
|
||||
}
|
||||
|
||||
public void FixedUpdate()
|
||||
{
|
||||
airdropParameters.Timer += 0.02f;
|
||||
|
||||
if (airdropParameters.Timer >= airdropParameters.TimeToStart && !airdropParameters.PlaneSpawned)
|
||||
{
|
||||
StartPlane();
|
||||
}
|
||||
|
||||
if (!airdropParameters.PlaneSpawned) return;
|
||||
|
||||
if (airdropParameters.DistanceTraveled >= airdropParameters.DistanceToDrop && !airdropParameters.BoxSpawned)
|
||||
{
|
||||
StartBox();
|
||||
BuildLootContainer();
|
||||
}
|
||||
|
||||
if (airdropParameters.DistanceTraveled < airdropParameters.DistanceToTravel)
|
||||
{
|
||||
airdropParameters.DistanceTraveled += Time.deltaTime * airdropParameters.Config.PlaneSpeed;
|
||||
var distanceToDrop = airdropParameters.DistanceToDrop - airdropParameters.DistanceTraveled;
|
||||
airdropPlane.ManualUpdate(distanceToDrop);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(airdropPlane.gameObject);
|
||||
Destroy(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartPlane()
|
||||
{
|
||||
airdropPlane.gameObject.SetActive(true);
|
||||
airdropParameters.PlaneSpawned = true;
|
||||
}
|
||||
|
||||
private void StartBox()
|
||||
{
|
||||
airdropParameters.BoxSpawned = true;
|
||||
var pointPos = airdropParameters.RandomAirdropPoint;
|
||||
var dropPos = new Vector3(pointPos.x, airdropParameters.DropHeight, pointPos.z);
|
||||
airdropBox.gameObject.SetActive(true);
|
||||
airdropBox.StartCoroutine(airdropBox.DropCrate(dropPos));
|
||||
}
|
||||
|
||||
private void BuildLootContainer()
|
||||
{
|
||||
factory.BuildContainer(airdropBox.container);
|
||||
factory.AddLoot(airdropBox.container);
|
||||
}
|
||||
|
||||
private void SetDistanceToDrop()
|
||||
{
|
||||
airdropParameters.DistanceToDrop = Vector3.Distance(
|
||||
new Vector3(airdropParameters.RandomAirdropPoint.x, airdropParameters.DropHeight, airdropParameters.RandomAirdropPoint.z),
|
||||
airdropPlane.transform.position);
|
||||
}
|
||||
}
|
||||
}
|
55
project/Aki.Custom/Airdrops/Models/AirdropConfigModel.cs
Normal file
55
project/Aki.Custom/Airdrops/Models/AirdropConfigModel.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Aki.Custom.Airdrops.Models
|
||||
{
|
||||
public class AirdropConfigModel
|
||||
{
|
||||
[JsonProperty("airdropChancePercent")]
|
||||
public AirdropChancePercent AirdropChancePercent { get; set; }
|
||||
|
||||
[JsonProperty("airdropMinStartTimeSeconds")]
|
||||
public int AirdropMinStartTimeSeconds { get; set; }
|
||||
|
||||
[JsonProperty("airdropMaxStartTimeSeconds")]
|
||||
public int AirdropMaxStartTimeSeconds { get; set; }
|
||||
|
||||
[JsonProperty("planeMinFlyHeight")]
|
||||
public int PlaneMinFlyHeight { get; set; }
|
||||
|
||||
[JsonProperty("planeMaxFlyHeight")]
|
||||
public int PlaneMaxFlyHeight { get; set; }
|
||||
|
||||
[JsonProperty("planeVolume")]
|
||||
public float PlaneVolume { get; set; }
|
||||
|
||||
[JsonProperty("planeSpeed")]
|
||||
public float PlaneSpeed { get; set; }
|
||||
|
||||
[JsonProperty("crateFallSpeed")]
|
||||
public float CrateFallSpeed { get; set; }
|
||||
}
|
||||
|
||||
public class AirdropChancePercent
|
||||
{
|
||||
[JsonProperty("bigmap")]
|
||||
public int Bigmap { get; set; }
|
||||
|
||||
[JsonProperty("woods")]
|
||||
public int Woods { get; set; }
|
||||
|
||||
[JsonProperty("lighthouse")]
|
||||
public int Lighthouse { get; set; }
|
||||
|
||||
[JsonProperty("shoreline")]
|
||||
public int Shoreline { get; set; }
|
||||
|
||||
[JsonProperty("interchange")]
|
||||
public int Interchange { get; set; }
|
||||
|
||||
[JsonProperty("reserve")]
|
||||
public int Reserve { get; set; }
|
||||
|
||||
[JsonProperty("tarkovStreets")]
|
||||
public int TarkovStreets { get; set; }
|
||||
}
|
||||
}
|
19
project/Aki.Custom/Airdrops/Models/AirdropLootModel.cs
Normal file
19
project/Aki.Custom/Airdrops/Models/AirdropLootModel.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Aki.Custom.Airdrops.Models
|
||||
{
|
||||
public class AirdropLootModel
|
||||
{
|
||||
[JsonProperty("tpl")]
|
||||
public string Tpl { get; set; }
|
||||
|
||||
[JsonProperty("isPreset")]
|
||||
public bool IsPreset { get; set; }
|
||||
|
||||
[JsonProperty("stackCount")]
|
||||
public int StackCount { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public string ID { get; set; }
|
||||
}
|
||||
}
|
20
project/Aki.Custom/Airdrops/Models/AirdropParametersModel.cs
Normal file
20
project/Aki.Custom/Airdrops/Models/AirdropParametersModel.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace Aki.Custom.Airdrops.Models
|
||||
{
|
||||
public class AirdropParametersModel
|
||||
{
|
||||
public AirdropConfigModel Config;
|
||||
|
||||
public bool AirdropAvailable;
|
||||
public float DistanceTraveled;
|
||||
public float DistanceToTravel;
|
||||
public float DistanceToDrop;
|
||||
public float Timer;
|
||||
public bool PlaneSpawned;
|
||||
public bool BoxSpawned;
|
||||
|
||||
public int DropHeight;
|
||||
public int TimeToStart;
|
||||
|
||||
public UnityEngine.Vector3 RandomAirdropPoint;
|
||||
}
|
||||
}
|
32
project/Aki.Custom/Airdrops/Patches/AirdropFlarePatch.cs
Normal file
32
project/Aki.Custom/Airdrops/Patches/AirdropFlarePatch.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using EFT.Airdrop;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Airdrops.Patches
|
||||
{
|
||||
public class AirdropFlarePatch : ModulePatch
|
||||
{
|
||||
private static readonly string[] _usableFlares = { "624c09cfbc2e27219346d955", "62389ba9a63f32501b1b4451" };
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(FlareCartridge).GetMethod(nameof(FlareCartridge.Init),
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix(BulletClass flareCartridge)
|
||||
{
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
var points = LocationScene.GetAll<AirdropPoint>().Any();
|
||||
|
||||
if (gameWorld != null && points && _usableFlares.Any(x => x == flareCartridge.Template._id))
|
||||
{
|
||||
gameWorld.gameObject.AddComponent<AirdropsManager>().isFlareDrop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
project/Aki.Custom/Airdrops/Patches/AirdropPatch.cs
Normal file
29
project/Aki.Custom/Airdrops/Patches/AirdropPatch.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using EFT.Airdrop;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Airdrops.Patches
|
||||
{
|
||||
public class AirdropPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(GameWorld).GetMethod(nameof(GameWorld.OnGameStarted));
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
public static void PatchPostFix()
|
||||
{
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
var points = LocationScene.GetAll<AirdropPoint>().Any();
|
||||
|
||||
if (gameWorld != null && points)
|
||||
{
|
||||
gameWorld.gameObject.AddComponent<AirdropsManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
127
project/Aki.Custom/Airdrops/Utils/AirdropUtil.cs
Normal file
127
project/Aki.Custom/Airdrops/Utils/AirdropUtil.cs
Normal file
@ -0,0 +1,127 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Custom.Airdrops.Models;
|
||||
using EFT;
|
||||
using EFT.Airdrop;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Aki.Custom.Airdrops.Utils
|
||||
{
|
||||
public static class AirdropUtil
|
||||
{
|
||||
public static AirdropConfigModel GetConfigFromServer()
|
||||
{
|
||||
string json = RequestHandler.GetJson("/singleplayer/airdrop/config");
|
||||
return JsonConvert.DeserializeObject<AirdropConfigModel>(json);
|
||||
}
|
||||
|
||||
public static int ChanceToSpawn(GameWorld gameWorld, AirdropConfigModel config, bool isFlare)
|
||||
{
|
||||
// Flare summoned airdrops are guaranteed
|
||||
if (isFlare)
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
string location = gameWorld.RegisteredPlayers[0].Location;
|
||||
|
||||
int result = 25;
|
||||
switch (location.ToLower())
|
||||
{
|
||||
case "bigmap":
|
||||
{
|
||||
result = config.AirdropChancePercent.Bigmap;
|
||||
break;
|
||||
}
|
||||
case "interchange":
|
||||
{
|
||||
result = config.AirdropChancePercent.Interchange;
|
||||
break;
|
||||
}
|
||||
case "rezervbase":
|
||||
{
|
||||
result = config.AirdropChancePercent.Reserve;
|
||||
break;
|
||||
}
|
||||
case "shoreline":
|
||||
{
|
||||
result = config.AirdropChancePercent.Shoreline;
|
||||
break;
|
||||
}
|
||||
case "woods":
|
||||
{
|
||||
result = config.AirdropChancePercent.Woods;
|
||||
break;
|
||||
}
|
||||
case "lighthouse":
|
||||
{
|
||||
result = config.AirdropChancePercent.Lighthouse;
|
||||
break;
|
||||
}
|
||||
case "tarkovstreets":
|
||||
{
|
||||
result = config.AirdropChancePercent.TarkovStreets;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool ShouldAirdropOccur(int dropChance, List<AirdropPoint> airdropPoints)
|
||||
{
|
||||
return airdropPoints.Count > 0 && Random.Range(0, 100) <= dropChance;
|
||||
}
|
||||
|
||||
public static AirdropParametersModel InitAirdropParams(GameWorld gameWorld, bool isFlare)
|
||||
{
|
||||
var serverConfig = GetConfigFromServer();
|
||||
var allAirdropPoints = LocationScene.GetAll<AirdropPoint>().ToList();
|
||||
var playerPosition = gameWorld.RegisteredPlayers[0].Position;
|
||||
var flareAirdropPoints = new List<AirdropPoint>();
|
||||
var dropChance = ChanceToSpawn(gameWorld, serverConfig, isFlare);
|
||||
|
||||
if (isFlare && allAirdropPoints.Count > 0)
|
||||
{
|
||||
foreach (AirdropPoint point in allAirdropPoints)
|
||||
{
|
||||
if (Vector3.Distance(playerPosition, point.transform.position) <= 100f)
|
||||
{
|
||||
flareAirdropPoints.Add(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flareAirdropPoints.Count == 0 && isFlare)
|
||||
{
|
||||
Debug.LogError($"[AKI-AIRDROPS]: Airdrop called in by flare, Unable to find an airdropPoint within 100m, defaulting to normal drop");
|
||||
flareAirdropPoints.Add(allAirdropPoints.OrderBy(_ => Guid.NewGuid()).FirstOrDefault());
|
||||
}
|
||||
|
||||
return new AirdropParametersModel()
|
||||
{
|
||||
Config = serverConfig,
|
||||
AirdropAvailable = ShouldAirdropOccur(dropChance, allAirdropPoints),
|
||||
|
||||
DistanceTraveled = 0f,
|
||||
DistanceToTravel = 8000f,
|
||||
Timer = 0,
|
||||
PlaneSpawned = false,
|
||||
BoxSpawned = false,
|
||||
|
||||
DropHeight = Random.Range(serverConfig.PlaneMinFlyHeight, serverConfig.PlaneMaxFlyHeight),
|
||||
TimeToStart = isFlare
|
||||
? 5
|
||||
: Random.Range(serverConfig.AirdropMinStartTimeSeconds, serverConfig.AirdropMaxStartTimeSeconds),
|
||||
|
||||
RandomAirdropPoint = isFlare && allAirdropPoints.Count > 0
|
||||
? flareAirdropPoints.OrderBy(_ => Guid.NewGuid()).First().transform.position
|
||||
: allAirdropPoints.OrderBy(_ => Guid.NewGuid()).First().transform.position
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
73
project/Aki.Custom/Airdrops/Utils/ItemFactoryUtil.cs
Normal file
73
project/Aki.Custom/Airdrops/Utils/ItemFactoryUtil.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using UnityEngine;
|
||||
using EFT.Interactive;
|
||||
using EFT.InventoryLogic;
|
||||
using Aki.Common.Http;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using Aki.Custom.Airdrops.Models;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aki.Custom.Airdrops.Utils
|
||||
{
|
||||
public class ItemFactoryUtil
|
||||
{
|
||||
private ItemFactory itemFactory;
|
||||
private static readonly string DropContainer = "6223349b3136504a544d1608";
|
||||
|
||||
public ItemFactoryUtil()
|
||||
{
|
||||
itemFactory = Singleton<ItemFactory>.Instance;
|
||||
}
|
||||
|
||||
public void BuildContainer(LootableContainer container)
|
||||
{
|
||||
if (itemFactory.ItemTemplates.TryGetValue(DropContainer, out var template))
|
||||
{
|
||||
Item item = itemFactory.CreateItem(DropContainer, template._id, null);
|
||||
LootItem.CreateLootContainer(container, item, "CRATE", Singleton<GameWorld>.Instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[AKI-AIRDROPS]: unable to find template: {DropContainer}");
|
||||
}
|
||||
}
|
||||
|
||||
public async void AddLoot(LootableContainer container)
|
||||
{
|
||||
List<AirdropLootModel> loot = GetLoot();
|
||||
|
||||
Item actualItem;
|
||||
|
||||
foreach (var item in loot)
|
||||
{
|
||||
ResourceKey[] resources;
|
||||
if (item.IsPreset)
|
||||
{
|
||||
actualItem = itemFactory.GetPresetItem(item.Tpl);
|
||||
resources = actualItem.GetAllItems().Select(x => x.Template).SelectMany(x => x.AllResources).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
actualItem = itemFactory.CreateItem(item.ID, item.Tpl, null);
|
||||
actualItem.StackObjectsCount = item.StackCount;
|
||||
|
||||
resources = actualItem.Template.AllResources.ToArray();
|
||||
}
|
||||
|
||||
container.ItemOwner.MainStorage[0].Add(actualItem);
|
||||
await Singleton<PoolManager>.Instance.LoadBundlesAndCreatePools(PoolManager.PoolsCategory.Raid, PoolManager.AssemblyType.Local, resources, JobPriority.Immediate, null, PoolManager.DefaultCancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private List<AirdropLootModel> GetLoot()
|
||||
{
|
||||
var json = RequestHandler.GetJson("/client/location/getAirdropLoot");
|
||||
var loot = JsonConvert.DeserializeObject<List<AirdropLootModel>>(json);
|
||||
|
||||
return loot;
|
||||
}
|
||||
}
|
||||
}
|
42
project/Aki.Custom/Aki.Custom.csproj
Normal file
42
project/Aki.Custom/Aki.Custom.csproj
Normal file
@ -0,0 +1,42 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AssemblyName>aki-custom</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Company>Aki</Company>
|
||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||
<Reference Include="Comfort" HintPath="..\Shared\Managed\Comfort.dll" Private="False" />
|
||||
<Reference Include="Sirenix.Serialization" HintPath="..\Shared\Managed\Sirenix.Serialization.dll" Private="False" />
|
||||
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.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.AudioModule" HintPath="..\Shared\Managed\UnityEngine.AudioModule.dll" Private="False" />
|
||||
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||
<Reference Include="UnityEngine.ParticleSystemModule" HintPath="..\Shared\Managed\UnityEngine.ParticleSystemModule.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.UIModule" HintPath="..\Shared\Managed\UnityEngine.UIModule.dll" Private="False" />
|
||||
<Reference Include="Unity.ScriptableBuildPipeline" HintPath="..\Shared\Managed\Unity.ScriptableBuildPipeline.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aki.PrePatch\Aki.PrePatch.csproj" />
|
||||
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
50
project/Aki.Custom/AkiCustomPlugin.cs
Normal file
50
project/Aki.Custom/AkiCustomPlugin.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Aki.Custom.Airdrops.Patches;
|
||||
using Aki.Custom.Patches;
|
||||
using Aki.Custom.Utils;
|
||||
using BepInEx;
|
||||
|
||||
namespace Aki.Custom
|
||||
{
|
||||
[BepInPlugin("com.spt-aki.custom", "AKI.Custom", "1.0.0")]
|
||||
class AkiCustomPlugin : BaseUnityPlugin
|
||||
{
|
||||
public AkiCustomPlugin()
|
||||
{
|
||||
Logger.LogInfo("Loading: Aki.Custom");
|
||||
|
||||
try
|
||||
{
|
||||
// Bundle patches should always load first
|
||||
BundleManager.GetBundles();
|
||||
new EasyAssetsPatch().Enable();
|
||||
new EasyBundlePatch().Enable();
|
||||
|
||||
new BossSpawnChancePatch().Enable();
|
||||
new BotDifficultyPatch().Enable();
|
||||
new CoreDifficultyPatch().Enable();
|
||||
new OfflineRaidMenuPatch().Enable();
|
||||
new SessionIdPatch().Enable();
|
||||
new VersionLabelPatch().Enable();
|
||||
new IsEnemyPatch().Enable();
|
||||
//new AddSelfAsEnemyPatch().Enable();
|
||||
new CheckAndAddEnemyPatch().Enable();
|
||||
new BotSelfEnemyPatch().Enable(); // needed
|
||||
new AddEnemyToAllGroupsInBotZonePatch().Enable();
|
||||
new AirdropPatch().Enable();
|
||||
new AirdropFlarePatch().Enable();
|
||||
new AddSptBotSettingsPatch().Enable();
|
||||
new CustomAiPatch().Enable();
|
||||
new ExitWhileLootingPatch().Enable();
|
||||
new QTEPatch().Enable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"{GetType().Name}: {ex}");
|
||||
throw;
|
||||
}
|
||||
|
||||
Logger.LogInfo("Completed: Aki.Custom");
|
||||
}
|
||||
}
|
||||
}
|
16
project/Aki.Custom/Models/BundleInfo.cs
Normal file
16
project/Aki.Custom/Models/BundleInfo.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Aki.Custom.Models
|
||||
{
|
||||
public class BundleInfo
|
||||
{
|
||||
public string Key { get; }
|
||||
public string Path { get; set; }
|
||||
public string[] DependencyKeys { get; }
|
||||
|
||||
public BundleInfo(string key, string path, string[] dependencyKeys)
|
||||
{
|
||||
Key = key;
|
||||
Path = path;
|
||||
DependencyKeys = dependencyKeys;
|
||||
}
|
||||
}
|
||||
}
|
9
project/Aki.Custom/Models/BundleItem.cs
Normal file
9
project/Aki.Custom/Models/BundleItem.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Aki.Custom.Models
|
||||
{
|
||||
public struct BundleItem
|
||||
{
|
||||
public string FileName;
|
||||
public uint Crc;
|
||||
public string[] Dependencies;
|
||||
}
|
||||
}
|
24
project/Aki.Custom/Models/DefaultRaidSettings.cs
Normal file
24
project/Aki.Custom/Models/DefaultRaidSettings.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using EFT.Bots;
|
||||
|
||||
namespace Aki.Custom.Models
|
||||
{
|
||||
public class DefaultRaidSettings
|
||||
{
|
||||
public EBotAmount AiAmount;
|
||||
public EBotDifficulty AiDifficulty;
|
||||
public bool BossEnabled;
|
||||
public bool ScavWars;
|
||||
public bool TaggedAndCursed;
|
||||
public bool EnablePve;
|
||||
|
||||
public DefaultRaidSettings(EBotAmount aiAmount, EBotDifficulty aiDifficulty, bool bossEnabled, bool scavWars, bool taggedAndCursed, bool enablePve)
|
||||
{
|
||||
AiAmount = aiAmount;
|
||||
AiDifficulty = aiDifficulty;
|
||||
BossEnabled = bossEnabled;
|
||||
ScavWars = scavWars;
|
||||
TaggedAndCursed = taggedAndCursed;
|
||||
EnablePve = enablePve;
|
||||
}
|
||||
}
|
||||
}
|
8
project/Aki.Custom/Models/VersionResponse.cs
Normal file
8
project/Aki.Custom/Models/VersionResponse.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Aki.Custom.Models
|
||||
{
|
||||
public struct VersionResponse
|
||||
{
|
||||
public string Version { get; set; }
|
||||
}
|
||||
}
|
||||
|
34
project/Aki.Custom/Patches/AddEnemyPatch.cs
Normal file
34
project/Aki.Custom/Patches/AddEnemyPatch.cs
Normal file
@ -0,0 +1,34 @@
|
||||
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(BotGroupClass).GetMethod(methodName);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(BotGroupClass __instance, IAIDetails 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
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(BotControllerClass) && type.GetMethod(methodName) != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {_targetType.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {methodName}");
|
||||
|
||||
return _targetType.GetMethod(methodName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AddEnemyToAllGroupsInBotZone()
|
||||
/// Goal: by default, AddEnemyToAllGroupsInBotZone doesn't check if the bot group is on the same side as the player.
|
||||
/// The effect of this is that when you are a Scav and kill a Usec, every bot group in the zone will aggro you including other Scavs.
|
||||
/// This should fix that.
|
||||
/// </summary>
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(BotControllerClass __instance, IAIDetails aggressor, IAIDetails groupOwner, IAIDetails target)
|
||||
{
|
||||
BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone;
|
||||
foreach (var item in __instance.Groups())
|
||||
{
|
||||
if (item.Key != botZone)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var group in item.Value.GetGroups(notNull: true))
|
||||
{
|
||||
bool differentSide = aggressor.Side != group.Side;
|
||||
bool sameSide = aggressor.Side == target.Side;
|
||||
|
||||
if (!group.Enemies.ContainsKey(aggressor)
|
||||
&& (differentSide || !sameSide)
|
||||
&& !group.HaveMemberWithRole(WildSpawnType.gifter)
|
||||
&& group.ShallRevengeFor(target)
|
||||
)
|
||||
{
|
||||
group.AddEnemy(aggressor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
28
project/Aki.Custom/Patches/AddSptBotSettingsPatch.cs
Normal file
28
project/Aki.Custom/Patches/AddSptBotSettingsPatch.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Aki.PrePatch;
|
||||
using Aki.Reflection.Patching;
|
||||
using EFT;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class AddSptBotSettingsPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(BotSettingsRepoClass).GetMethod("Init");
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static void PatchPrefix(ref Dictionary<WildSpawnType, BotSettingsValuesClass> ___dictionary_0)
|
||||
{
|
||||
if (___dictionary_0.ContainsKey((WildSpawnType)AkiBotsPrePatcher.sptUsecValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
___dictionary_0.Add((WildSpawnType)AkiBotsPrePatcher.sptUsecValue, new BotSettingsValuesClass(false, false, false, EPlayerSide.Savage.ToStringNoBox()));
|
||||
___dictionary_0.Add((WildSpawnType)AkiBotsPrePatcher.sptBearValue, new BotSettingsValuesClass(false, false, false, EPlayerSide.Savage.ToStringNoBox()));
|
||||
}
|
||||
}
|
||||
}
|
56
project/Aki.Custom/Patches/BossSpawnChancePatch.cs
Normal file
56
project/Aki.Custom/Patches/BossSpawnChancePatch.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
/// <summary>
|
||||
/// Boss spawn chance is 100%, all the time, this patch adjusts the chance to the maps boss wave value
|
||||
/// </summary>
|
||||
public class BossSpawnChancePatch : ModulePatch
|
||||
{
|
||||
private static float[] _bossSpawnPercent;
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = PatchConstants.LocalGameType;
|
||||
var desiredMethod = desiredType
|
||||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)
|
||||
.SingleOrDefault(m => IsTargetMethod(m));
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
private static bool IsTargetMethod(MethodInfo mi)
|
||||
{
|
||||
var parameters = mi.GetParameters();
|
||||
return (parameters.Length == 2
|
||||
&& parameters[0].Name == "wavesSettings"
|
||||
&& parameters[1].Name == "bossLocationSpawn");
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static void PatchPrefix(BossLocationSpawn[] bossLocationSpawn)
|
||||
{
|
||||
_bossSpawnPercent = bossLocationSpawn.Select(s => s.BossChance).ToArray();
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix(ref BossLocationSpawn[] __result)
|
||||
{
|
||||
if (__result.Length != _bossSpawnPercent.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _bossSpawnPercent.Length; i++)
|
||||
{
|
||||
__result[i].BossChance = _bossSpawnPercent[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
project/Aki.Custom/Patches/BotDifficultyPatch.cs
Normal file
28
project/Aki.Custom/Patches/BotDifficultyPatch.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Aki.Common.Http;
|
||||
using EFT;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class BotDifficultyPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var methodName = "LoadDifficultyStringInternal";
|
||||
var flags = BindingFlags.Public | BindingFlags.Static;
|
||||
|
||||
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
|
||||
.GetMethod(methodName, flags);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref string __result, BotDifficulty botDifficulty, WildSpawnType role)
|
||||
{
|
||||
__result = RequestHandler.GetJson($"/singleplayer/settings/bot/difficulty/{role}/{botDifficulty}");
|
||||
return string.IsNullOrWhiteSpace(__result);
|
||||
}
|
||||
}
|
||||
}
|
73
project/Aki.Custom/Patches/BotEnemyTargetPatch.cs
Normal file
73
project/Aki.Custom/Patches/BotEnemyTargetPatch.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
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(BotControllerClass) && type.GetMethod(methodName) != null)
|
||||
{
|
||||
Logger.LogInfo($"{methodName}: {type.FullName}");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return _targetType.GetMethod(methodName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AddEnemyToAllGroupsInBotZone()
|
||||
/// Goal: by default, AddEnemyToAllGroupsInBotZone doesn't check if the bot group is on the same side as the player.
|
||||
/// The effect of this is that when you are a Scav and kill a Usec, every bot group in the zone will aggro you including other Scavs.
|
||||
/// This should fix that.
|
||||
/// </summary>
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(BotControllerClass __instance, IAIDetails aggressor, IAIDetails groupOwner, IAIDetails target)
|
||||
{
|
||||
BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone;
|
||||
foreach (var item in __instance.Groups())
|
||||
{
|
||||
if (item.Key != botZone)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var group in item.Value.GetGroups(notNull: true))
|
||||
{
|
||||
if (!group.Enemies.ContainsKey(aggressor) && ShouldAttack(aggressor, target, group))
|
||||
{
|
||||
group.AddEnemy(aggressor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
private static bool ShouldAttack(IAIDetails attacker, IAIDetails victim, BotGroupClass groupToCheck)
|
||||
{
|
||||
// Group should target if player attack a victim on the same side or if the group is not on the same side as the player.
|
||||
bool shouldAttack = attacker.Side != groupToCheck.Side
|
||||
|| attacker.Side == victim.Side;
|
||||
|
||||
return !groupToCheck.HaveMemberWithRole(WildSpawnType.gifter) && groupToCheck.ShallRevengeFor(victim) && shouldAttack;
|
||||
}
|
||||
}
|
||||
}
|
41
project/Aki.Custom/Patches/BotSelfEnemyPatch.cs
Normal file
41
project/Aki.Custom/Patches/BotSelfEnemyPatch.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using EFT;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
/// <summary>
|
||||
/// Goal: patch removes the current bot from its own enemy list - occurs when adding bots type to its enemy array in difficulty settings
|
||||
/// </summary>
|
||||
internal class BotSelfEnemyPatch : ModulePatch
|
||||
{
|
||||
private static readonly string methodName = "PreActivate";
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(BotOwner).GetMethod(methodName);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(BotOwner __instance, BotGroupClass group)
|
||||
{
|
||||
IAIDetails selfToRemove = null;
|
||||
|
||||
foreach (var enemy in group.Enemies)
|
||||
{
|
||||
if (enemy.Key.Id == __instance.Id)
|
||||
{
|
||||
selfToRemove = enemy.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selfToRemove != null)
|
||||
{
|
||||
group.Enemies.Remove(selfToRemove);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
73
project/Aki.Custom/Patches/CheckAndAddEnemyPatch.cs
Normal file
73
project/Aki.Custom/Patches/CheckAndAddEnemyPatch.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class CheckAndAddEnemyPatch : ModulePatch
|
||||
{
|
||||
private static Type _targetType;
|
||||
private static FieldInfo _sideField;
|
||||
private static FieldInfo _enemiesField;
|
||||
private static FieldInfo _spawnTypeField;
|
||||
private static MethodInfo _addEnemy;
|
||||
private readonly string _targetMethodName = "CheckAndAddEnemy";
|
||||
|
||||
public CheckAndAddEnemyPatch()
|
||||
{
|
||||
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
|
||||
_sideField = _targetType.GetField("Side");
|
||||
_enemiesField = _targetType.GetField("Enemies");
|
||||
_spawnTypeField = _targetType.GetField("wildSpawnType_0", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
_addEnemy = _targetType.GetMethod("AddEnemy");
|
||||
}
|
||||
|
||||
private bool IsTargetType(Type type)
|
||||
{
|
||||
if (type.GetMethod("AddEnemy") != null && type.GetMethod("AddEnemyGroupIfAllowed") != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return _targetType.GetMethod(_targetMethodName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CheckAndAddEnemy()
|
||||
/// Goal: This patch lets bosses shoot back once a PMC has shot them
|
||||
/// removes the !player.AIData.IsAI check
|
||||
/// </summary>
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(object __instance, ref IAIDetails player, ref bool ignoreAI)
|
||||
{
|
||||
//var side = (EPlayerSide)_sideField.GetValue(__instance);
|
||||
//var botType = (WildSpawnType)_spawnTypeField.GetValue(__instance);
|
||||
|
||||
if (!player.HealthController.IsAlive)
|
||||
{
|
||||
return false; // do nothing and skip
|
||||
}
|
||||
|
||||
var enemies = (Dictionary<IAIDetails, BotSettingsClass>)_enemiesField.GetValue(__instance);
|
||||
if (enemies.ContainsKey(player))
|
||||
{
|
||||
return false;// do nothing and skip
|
||||
}
|
||||
|
||||
// Add enemy to list
|
||||
//if (!enemies.ContainsKey(player) && (!playerIsAi || ignoreAI))
|
||||
_addEnemy.Invoke(__instance, new IAIDetails[] { player });
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
27
project/Aki.Custom/Patches/CoreDifficultyPatch.cs
Normal file
27
project/Aki.Custom/Patches/CoreDifficultyPatch.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Aki.Common.Http;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class CoreDifficultyPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var methodName = "LoadCoreByString";
|
||||
var flags = BindingFlags.Public | BindingFlags.Static;
|
||||
|
||||
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
|
||||
.GetMethod(methodName, flags);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref string __result)
|
||||
{
|
||||
__result = RequestHandler.GetJson("/singleplayer/settings/bot/difficulty/core/core");
|
||||
return string.IsNullOrWhiteSpace(__result);
|
||||
}
|
||||
}
|
||||
}
|
148
project/Aki.Custom/Patches/CustomAiPatch.cs
Normal file
148
project/Aki.Custom/Patches/CustomAiPatch.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Reflection.Patching;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Aki.PrePatch;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class CustomAiPatch : ModulePatch
|
||||
{
|
||||
private static readonly Random random = new Random();
|
||||
private static Dictionary<WildSpawnType, Dictionary<string, Dictionary<string, int>>> botTypeCache = new Dictionary<WildSpawnType, Dictionary<string, Dictionary<string, int>>>();
|
||||
private static DateTime cacheDate = new DateTime();
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(BotBrainClass).GetMethod("Activate", BindingFlags.Public | BindingFlags.Instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a randomly picked wildspawntype from server and change PMC bot to use it, this ensures the bot is generated with that random type altering its behaviour
|
||||
/// </summary>
|
||||
/// <param name="__state">state to save for postfix to use later</param>
|
||||
/// <param name="__instance"></param>
|
||||
/// <param name="___botOwner_0">botOwner_0 property</param>
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(out WildSpawnType __state, object __instance, BotOwner ___botOwner_0)
|
||||
{
|
||||
// Store original type in state param
|
||||
__state = ___botOwner_0.Profile.Info.Settings.Role;
|
||||
//Console.WriteLine($"Processing bot {___botOwner_0.Profile.Info.Nickname} with role {___botOwner_0.Profile.Info.Settings.Role}");
|
||||
try
|
||||
{
|
||||
if (BotIsSptPmc(___botOwner_0.Profile.Info.Settings.Role))
|
||||
{
|
||||
string currentMapName = GetCurrentMap();
|
||||
|
||||
if (!botTypeCache.TryGetValue(___botOwner_0.Profile.Info.Settings.Role, out var botSettings) || CacheIsStale())
|
||||
{
|
||||
ResetCacheDate();
|
||||
HydrateCacheWithServerData();
|
||||
|
||||
if (!botTypeCache.TryGetValue(___botOwner_0.Profile.Info.Settings.Role, out botSettings))
|
||||
{
|
||||
throw new Exception($"Bots were refreshed from the server but the cache still doesnt contain an appropriate bot for type {___botOwner_0.Profile.Info.Settings.Role}");
|
||||
}
|
||||
}
|
||||
|
||||
var mapSettings = botSettings[currentMapName.ToLower()];
|
||||
var randomType = WeightedRandom(mapSettings.Keys.ToArray(), mapSettings.Values.ToArray());
|
||||
if (Enum.TryParse(randomType, out WildSpawnType newAiType))
|
||||
{
|
||||
Console.WriteLine($"Updated spt bot {___botOwner_0.Profile.Info.Nickname}: {___botOwner_0.Profile.Info.Settings.Role} to {newAiType}");
|
||||
___botOwner_0.Profile.Info.Settings.Role = newAiType;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Couldnt not update spt bot {___botOwner_0.Profile.Info.Nickname} to the new type, random type {randomType} does not exist for WildSpawnType");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error processing log: {ex.Message}");
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
}
|
||||
|
||||
return true; // Do original
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revert prefix change, get bots type back to what it was before changes
|
||||
/// </summary>
|
||||
/// <param name="__state">Saved state from prefix patch</param>
|
||||
/// <param name="___botOwner_0">botOwner_0 property</param>
|
||||
[PatchPostfix]
|
||||
private static void PatchPostFix(WildSpawnType __state, BotOwner ___botOwner_0)
|
||||
{
|
||||
if (BotIsSptPmc(__state))
|
||||
{
|
||||
// Set spt bot bot back to original type
|
||||
___botOwner_0.Profile.Info.Settings.Role = __state;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool BotIsSptPmc(WildSpawnType role)
|
||||
{
|
||||
return (long)role == -2147483648 || (long)role == 0;
|
||||
}
|
||||
|
||||
private static string GetCurrentMap()
|
||||
{
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
|
||||
return gameWorld.RegisteredPlayers[0].Location;
|
||||
}
|
||||
|
||||
private static bool CacheIsStale()
|
||||
{
|
||||
TimeSpan cacheAge = DateTime.Now - cacheDate;
|
||||
|
||||
return cacheAge.Minutes > 20;
|
||||
}
|
||||
|
||||
private static void ResetCacheDate()
|
||||
{
|
||||
cacheDate = DateTime.Now;
|
||||
}
|
||||
|
||||
private static void HydrateCacheWithServerData()
|
||||
{
|
||||
// Get weightings for PMCs from server and store in dict
|
||||
var result = RequestHandler.GetJson($"/singleplayer/settings/bot/getBotBehaviours/");
|
||||
botTypeCache = JsonConvert.DeserializeObject<Dictionary<WildSpawnType, Dictionary<string, Dictionary<string, int>>>>(result);
|
||||
Console.WriteLine($"cached: {botTypeCache.Count} bots");
|
||||
}
|
||||
|
||||
private static string WeightedRandom(string[] botTypes, int[] weights)
|
||||
{
|
||||
var cumulativeWeights = new int[botTypes.Length];
|
||||
|
||||
for (int i = 0; i < weights.Length; i++)
|
||||
{
|
||||
cumulativeWeights[i] = weights[i] + (i == 0 ? 0 : cumulativeWeights[i - 1]);
|
||||
}
|
||||
|
||||
var maxCumulativeWeight = cumulativeWeights[cumulativeWeights.Length - 1];
|
||||
var randomNumber = maxCumulativeWeight * random.NextDouble();
|
||||
|
||||
for (var itemIndex = 0; itemIndex < botTypes.Length; itemIndex++)
|
||||
{
|
||||
if (cumulativeWeights[itemIndex] >= randomNumber)
|
||||
{
|
||||
return botTypes[itemIndex];
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("failed to get random bot weighting, returned assault");
|
||||
return "assault";
|
||||
}
|
||||
}
|
||||
}
|
137
project/Aki.Custom/Patches/EasyAssetsPatch.cs
Normal file
137
project/Aki.Custom/Patches/EasyAssetsPatch.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Diz.Jobs;
|
||||
using Diz.Resources;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Build.Pipeline;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Aki.Custom.Models;
|
||||
using Aki.Custom.Utils;
|
||||
using DependencyGraph = DependencyGraph<IEasyBundle>;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class EasyAssetsPatch : ModulePatch
|
||||
{
|
||||
private static readonly FieldInfo _manifestField;
|
||||
private static readonly FieldInfo _bundlesField;
|
||||
private static readonly PropertyInfo _systemProperty;
|
||||
|
||||
static EasyAssetsPatch()
|
||||
{
|
||||
var type = typeof(EasyAssets);
|
||||
|
||||
_manifestField = type.GetField(nameof(EasyAssets.Manifest));
|
||||
_bundlesField = type.GetField($"{EasyBundleHelper.Type.Name.ToLowerInvariant()}_0", PatchConstants.PrivateFlags);
|
||||
_systemProperty = type.GetProperty("System");
|
||||
}
|
||||
|
||||
public EasyAssetsPatch()
|
||||
{
|
||||
_ = nameof(IEasyBundle.SameNameAsset);
|
||||
_ = nameof(IBundleLock.IsLocked);
|
||||
_ = nameof(BundleLock.MaxConcurrentOperations);
|
||||
_ = nameof(DependencyGraph.GetDefaultNode);
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(EasyAssets).GetMethods(PatchConstants.PrivateFlags).Single(IsTargetMethod);
|
||||
}
|
||||
|
||||
private static bool IsTargetMethod(MethodInfo mi)
|
||||
{
|
||||
var parameters = mi.GetParameters();
|
||||
return (parameters.Length == 6
|
||||
&& parameters[0].Name == "bundleLock"
|
||||
&& parameters[1].Name == "defaultKey"
|
||||
&& parameters[4].Name == "shouldExclude");
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref Task __result, EasyAssets __instance, [CanBeNull] IBundleLock bundleLock, string defaultKey, string rootPath,
|
||||
string platformName, [CanBeNull] Func<string, bool> shouldExclude, [CanBeNull] Func<string, Task> bundleCheck)
|
||||
{
|
||||
__result = Init(__instance, bundleLock, defaultKey, rootPath, platformName, shouldExclude, bundleCheck);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string GetPairKey(KeyValuePair<string, BundleItem> x)
|
||||
{
|
||||
return x.Key;
|
||||
}
|
||||
|
||||
public static BundleDetails GetPairValue(KeyValuePair<string, BundleItem> x)
|
||||
{
|
||||
return new BundleDetails
|
||||
{
|
||||
FileName = x.Value.FileName,
|
||||
Crc = x.Value.Crc,
|
||||
Dependencies = x.Value.Dependencies
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task<CompatibilityAssetBundleManifest> GetManifestBundle(string filepath)
|
||||
{
|
||||
var manifestLoading = AssetBundle.LoadFromFileAsync(filepath);
|
||||
await manifestLoading.Await();
|
||||
|
||||
var assetBundle = manifestLoading.assetBundle;
|
||||
var assetLoading = assetBundle.LoadAllAssetsAsync();
|
||||
await assetLoading.Await();
|
||||
|
||||
return (CompatibilityAssetBundleManifest)assetLoading.allAssets[0];
|
||||
}
|
||||
|
||||
private static async Task<CompatibilityAssetBundleManifest> GetManifestJson(string filepath)
|
||||
{
|
||||
var text = string.Empty;
|
||||
|
||||
using (var reader = File.OpenText($"{filepath}.json"))
|
||||
{
|
||||
text = await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
var data = JsonConvert.DeserializeObject<Dictionary<string, BundleItem>>(text).ToDictionary(GetPairKey, GetPairValue);
|
||||
var manifest = ScriptableObject.CreateInstance<CompatibilityAssetBundleManifest>();
|
||||
manifest.SetResults(data);
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// platform manifest
|
||||
var path = $"{rootPath.Replace("file:///", string.Empty).Replace("file://", string.Empty)}/{platformName}/";
|
||||
var filepath = path + platformName;
|
||||
var manifest = (File.Exists(filepath)) ? await GetManifestBundle(filepath) : await GetManifestJson(filepath);
|
||||
|
||||
// load bundles
|
||||
var bundleNames = manifest.GetAllAssetBundles().Union(BundleManager.Bundles.Keys).ToArray();
|
||||
var bundles = (IEasyBundle[])Array.CreateInstance(EasyBundleHelper.Type, bundleNames.Length);
|
||||
|
||||
if (bundleLock == null)
|
||||
{
|
||||
bundleLock = new BundleLock(int.MaxValue);
|
||||
}
|
||||
|
||||
for (var i = 0; i < bundleNames.Length; i++)
|
||||
{
|
||||
bundles[i] = (IEasyBundle)Activator.CreateInstance(EasyBundleHelper.Type, new object[] { bundleNames[i], path, manifest, bundleLock, bundleCheck });
|
||||
await JobScheduler.Yield(EJobPriority.Immediate);
|
||||
}
|
||||
|
||||
_manifestField.SetValue(instance, manifest);
|
||||
_bundlesField.SetValue(instance, bundles);
|
||||
_systemProperty.SetValue(instance, new DependencyGraph(bundles, defaultKey, shouldExclude));
|
||||
}
|
||||
}
|
||||
}
|
49
project/Aki.Custom/Patches/EasyBundlePatch.cs
Normal file
49
project/Aki.Custom/Patches/EasyBundlePatch.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Diz.DependencyManager;
|
||||
using UnityEngine.Build.Pipeline;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Aki.Custom.Models;
|
||||
using Aki.Custom.Utils;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class EasyBundlePatch : ModulePatch
|
||||
{
|
||||
static EasyBundlePatch()
|
||||
{
|
||||
_ = nameof(IEasyBundle.SameNameAsset);
|
||||
_ = nameof(IBundleLock.IsLocked);
|
||||
_ = nameof(BindableState<ELoadState>.Bind);
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return EasyBundleHelper.Type.GetConstructors()[0];
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix(object __instance, string key, string rootPath, CompatibilityAssetBundleManifest manifest, IBundleLock bundleLock)
|
||||
{
|
||||
var path = rootPath + key;
|
||||
var dependencyKeys = manifest.GetDirectDependencies(key) ?? new string[0];
|
||||
|
||||
if (BundleManager.Bundles.TryGetValue(key, out BundleInfo bundle))
|
||||
{
|
||||
dependencyKeys = (dependencyKeys.Length > 0) ? dependencyKeys.Union(bundle.DependencyKeys).ToArray() : bundle.DependencyKeys;
|
||||
path = bundle.Path;
|
||||
}
|
||||
|
||||
_ = new EasyBundleHelper(__instance)
|
||||
{
|
||||
Key = key,
|
||||
Path = path,
|
||||
KeyWithoutExtension = Path.GetFileNameWithoutExtension(key),
|
||||
DependencyKeys = dependencyKeys,
|
||||
LoadState = new BindableState<ELoadState>(ELoadState.Unloaded, null),
|
||||
BundleLock = bundleLock
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
42
project/Aki.Custom/Patches/ExitWhileLootingPatch.cs
Normal file
42
project/Aki.Custom/Patches/ExitWhileLootingPatch.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
/// <summary>
|
||||
/// Fix a bsg bug that causes the game to soft-lock when you have a container opened when extracting
|
||||
/// </summary>
|
||||
public class ExitWhileLootingPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return PatchConstants.EftTypes.Single(x => x.Name == "LocalGame").BaseType // BaseLocalGame
|
||||
.GetMethod("Stop", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
}
|
||||
|
||||
// Look at BaseLocalGame<TPlayerOwner> and find a method named "Stop"
|
||||
// once you find it, there should be a StartBlackScreenShow method with
|
||||
// a callback method (on dnspy will be called @class.method_0)
|
||||
// Go into that method. This will be part of the code:
|
||||
// if (GClass2505.CheckCurrentScreen(EScreenType.Reconnect))
|
||||
// {
|
||||
// GClass2505.CloseAllScreensForced();
|
||||
// }
|
||||
// The code INSIDE the if needs to run
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(string profileId)
|
||||
{
|
||||
var player = Singleton<GameWorld>.Instance.MainPlayer;
|
||||
if (profileId == player?.Profile.Id)
|
||||
{
|
||||
GClass2727.Instance.CloseAllScreensForced();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
121
project/Aki.Custom/Patches/IsEnemyPatch.cs
Normal file
121
project/Aki.Custom/Patches/IsEnemyPatch.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
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()
|
||||
{
|
||||
return _targetType.GetMethod(_targetMethodName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IsEnemy()
|
||||
/// Goal: Make bots take Side into account when deciding if another player/bot is an enemy
|
||||
/// Check enemy cache list first, if not found, check side, if they differ, add to enemy list and return true
|
||||
/// Needed to ensure bot checks the enemy side, not just its botType
|
||||
/// </summary>
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref bool __result, BotGroupClass __instance, IAIDetails requester)
|
||||
{
|
||||
var isEnemy = false; // default not an enemy
|
||||
|
||||
// Check existing enemies list
|
||||
if (__instance.Enemies.Any(x=> x.Value.Player.Id == requester.Id))
|
||||
{
|
||||
isEnemy = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (__instance.Side == EPlayerSide.Usec)
|
||||
{
|
||||
if (requester.Side == EPlayerSide.Bear || requester.Side == EPlayerSide.Savage ||
|
||||
ShouldAttackUsec(requester))
|
||||
{
|
||||
isEnemy = true;
|
||||
__instance.AddEnemy(requester);
|
||||
}
|
||||
}
|
||||
else if (__instance.Side == EPlayerSide.Bear)
|
||||
{
|
||||
if (requester.Side == EPlayerSide.Usec || requester.Side == EPlayerSide.Savage ||
|
||||
ShouldAttackBear(requester))
|
||||
{
|
||||
isEnemy = true;
|
||||
__instance.AddEnemy(requester);
|
||||
}
|
||||
}
|
||||
else if (__instance.Side == EPlayerSide.Savage)
|
||||
{
|
||||
if (requester.Side != EPlayerSide.Savage)
|
||||
{
|
||||
// everyone else is an enemy to savage (scavs)
|
||||
isEnemy = true;
|
||||
__instance.AddEnemy(requester);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__result = isEnemy;
|
||||
|
||||
return true; // Skip original
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return True when usec default behavior is attack + bot is usec
|
||||
/// </summary>
|
||||
/// <param name="requester"></param>
|
||||
/// <returns>bool</returns>
|
||||
private static bool ShouldAttackUsec(IAIDetails requester)
|
||||
{
|
||||
var requesterMind = requester?.AIData?.BotOwner?.Settings?.FileSettings?.Mind;
|
||||
|
||||
if (requesterMind == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return requester.IsAI && requesterMind.DEFAULT_USEC_BEHAVIOUR == EWarnBehaviour.Attack && requester.Side == EPlayerSide.Usec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return True when bear default behavior is attack + bot is bear
|
||||
/// </summary>
|
||||
/// <param name="requester"></param>
|
||||
/// <returns></returns>
|
||||
private static bool ShouldAttackBear(IAIDetails requester)
|
||||
{
|
||||
var requesterMind = requester.AIData?.BotOwner?.Settings?.FileSettings?.Mind;
|
||||
|
||||
if (requesterMind == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return requester.IsAI && requesterMind.DEFAULT_BEAR_BEHAVIOUR == EWarnBehaviour.Attack && requester.Side == EPlayerSide.Bear;
|
||||
}
|
||||
}
|
||||
}
|
69
project/Aki.Custom/Patches/OfflineRaidMenuPatch.cs
Normal file
69
project/Aki.Custom/Patches/OfflineRaidMenuPatch.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Common.Utils;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Custom.Models;
|
||||
using EFT.UI;
|
||||
using EFT.UI.Matchmaker;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using EFT;
|
||||
using static EFT.UI.Matchmaker.MatchmakerOfflineRaidScreen;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class OfflineRaidMenuPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = typeof(MatchmakerOfflineRaidScreen);
|
||||
var desiredMethod = desiredType.GetMethod(nameof(MatchmakerOfflineRaidScreen.Show));
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static void PatchPrefix(GClass2769 controller, UpdatableToggle ____offlineModeToggle)
|
||||
{
|
||||
var raidSettings = controller.RaidSettings;
|
||||
|
||||
raidSettings.RaidMode = ERaidMode.Local;
|
||||
raidSettings.BotSettings.IsEnabled = true;
|
||||
|
||||
// Default checkbox to be ticked
|
||||
____offlineModeToggle.isOn = true;
|
||||
|
||||
// get settings from server
|
||||
var json = RequestHandler.GetJson("/singleplayer/settings/raid/menu");
|
||||
var settings = Json.Deserialize<DefaultRaidSettings>(json);
|
||||
|
||||
// TODO: Not all settings are used and they also don't cover all the new settings that are available client-side
|
||||
if (settings != null)
|
||||
{
|
||||
raidSettings.BotSettings.BotAmount = settings.AiAmount;
|
||||
raidSettings.WavesSettings.BotAmount = settings.AiAmount;
|
||||
|
||||
raidSettings.WavesSettings.BotDifficulty = settings.AiDifficulty;
|
||||
|
||||
raidSettings.WavesSettings.IsBosses = settings.BossEnabled;
|
||||
|
||||
raidSettings.BotSettings.IsScavWars = false;
|
||||
|
||||
raidSettings.WavesSettings.IsTaggedAndCursed = settings.TaggedAndCursed;
|
||||
}
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix()
|
||||
{
|
||||
// disable "no progression save" panel
|
||||
var offlineRaidScreenContent = GameObject.Find("Matchmaker Offline Raid Screen").transform.Find("Content").transform;
|
||||
var warningPanel = offlineRaidScreenContent.Find("WarningPanelHorLayout");
|
||||
warningPanel.gameObject.SetActive(false);
|
||||
var spacer = offlineRaidScreenContent.Find("Space (1)");
|
||||
spacer.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
25
project/Aki.Custom/Patches/QTEPatch.cs
Normal file
25
project/Aki.Custom/Patches/QTEPatch.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Reflection.Patching;
|
||||
using System.Reflection;
|
||||
using EFT;
|
||||
using Aki.Reflection.Utils;
|
||||
using System.Linq;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class QTEPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod() => typeof(HideoutPlayerOwner).GetMethod(nameof(HideoutPlayerOwner.StopWorkout));
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix(HideoutPlayerOwner __instance)
|
||||
{
|
||||
RequestHandler.PutJson("/client/hideout/workout", new
|
||||
{
|
||||
skills = __instance.HideoutPlayer.Skills,
|
||||
effects = __instance.HideoutPlayer.HealthController.BodyPartEffects
|
||||
}
|
||||
.ToJson());
|
||||
}
|
||||
}
|
||||
}
|
39
project/Aki.Custom/Patches/SessionIdPatch.cs
Normal file
39
project/Aki.Custom/Patches/SessionIdPatch.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT.UI;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class SessionIdPatch : ModulePatch
|
||||
{
|
||||
private static PreloaderUI _preloader;
|
||||
|
||||
static SessionIdPatch()
|
||||
{
|
||||
_preloader = null;
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return PatchConstants.LocalGameType.BaseType.GetMethod("method_5", PatchConstants.PrivateFlags);
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix()
|
||||
{
|
||||
if (_preloader == null)
|
||||
{
|
||||
_preloader = Object.FindObjectOfType<PreloaderUI>();
|
||||
}
|
||||
|
||||
if (_preloader != null)
|
||||
{
|
||||
var raidID = Path.GetRandomFileName().Replace(".", string.Empty).Substring(0, 6).ToUpperInvariant();
|
||||
_preloader.SetSessionId(raidID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
project/Aki.Custom/Patches/VersionLabelPatch.cs
Normal file
48
project/Aki.Custom/Patches/VersionLabelPatch.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Common.Utils;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Aki.Custom.Models;
|
||||
using EFT.UI;
|
||||
using HarmonyLib;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class VersionLabelPatch : ModulePatch
|
||||
{
|
||||
private static string _versionLabel;
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
try
|
||||
{
|
||||
return PatchConstants.EftTypes
|
||||
.Single(x => x.GetField("Taxonomy", BindingFlags.Public | BindingFlags.Instance) != null)
|
||||
.GetMethod("Create", BindingFlags.Public | BindingFlags.Static);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Logger.LogInfo($"VersionLabelPatch failed {e.Message} {e.StackTrace} {e.InnerException.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
internal static void PatchPostfix(object __result)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_versionLabel))
|
||||
{
|
||||
var json = RequestHandler.GetJson("/singleplayer/settings/version");
|
||||
_versionLabel = Json.Deserialize<VersionResponse>(json).Version;
|
||||
Logger.LogInfo($"Server version: {_versionLabel}");
|
||||
}
|
||||
|
||||
Traverse.Create(MonoBehaviourSingleton<PreloaderUI>.Instance).Field("_alphaVersionLabel").Property("LocalizationKey").SetValue("{0}");
|
||||
Traverse.Create(MonoBehaviourSingleton<PreloaderUI>.Instance).Field("string_2").SetValue(_versionLabel);
|
||||
Traverse.Create(__result).Field("Major").SetValue(_versionLabel);
|
||||
}
|
||||
}
|
||||
}
|
50
project/Aki.Custom/Utils/BundleManager.cs
Normal file
50
project/Aki.Custom/Utils/BundleManager.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Aki.Common.Http;
|
||||
using Aki.Common.Utils;
|
||||
using Aki.Custom.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Aki.Custom.Utils
|
||||
{
|
||||
public static class BundleManager
|
||||
{
|
||||
public const string CachePath = "user/cache/bundles/";
|
||||
public static Dictionary<string, BundleInfo> Bundles { get; private set; }
|
||||
|
||||
static BundleManager()
|
||||
{
|
||||
Bundles = new Dictionary<string, BundleInfo>();
|
||||
|
||||
if (VFS.Exists(CachePath))
|
||||
{
|
||||
VFS.DeleteDirectory(CachePath);
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetBundles()
|
||||
{
|
||||
var json = RequestHandler.GetJson("/singleplayer/bundles");
|
||||
var jArray = JArray.Parse(json);
|
||||
|
||||
foreach (var jObj in jArray)
|
||||
{
|
||||
var key = jObj["key"].ToString();
|
||||
var path = jObj["path"].ToString();
|
||||
var bundle = new BundleInfo(key, path, jObj["dependencyKeys"].ToObject<string[]>());
|
||||
|
||||
if (path.Contains("http"))
|
||||
{
|
||||
var filepath = CachePath + Regex.Split(path, "bundle/", RegexOptions.IgnoreCase)[1];
|
||||
var data = RequestHandler.GetData(path, true);
|
||||
VFS.WriteFile(filepath, data);
|
||||
bundle.Path = filepath;
|
||||
}
|
||||
|
||||
Bundles.Add(key, bundle);
|
||||
}
|
||||
|
||||
VFS.WriteTextFile(CachePath + "bundles.json", Json.Serialize<Dictionary<string, BundleInfo>>(Bundles));
|
||||
}
|
||||
}
|
||||
}
|
121
project/Aki.Custom/Utils/EasyBundleHelper.cs
Normal file
121
project/Aki.Custom/Utils/EasyBundleHelper.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Aki.Reflection.Utils;
|
||||
using BindableState = BindableState<Diz.DependencyManager.ELoadState>;
|
||||
|
||||
namespace Aki.Custom.Utils
|
||||
{
|
||||
public class EasyBundleHelper
|
||||
{
|
||||
private const BindingFlags _flags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
private static readonly FieldInfo _pathField;
|
||||
private static readonly FieldInfo _keyWithoutExtensionField;
|
||||
private static readonly FieldInfo _bundleLockField;
|
||||
private static readonly PropertyInfo _dependencyKeysProperty;
|
||||
private static readonly PropertyInfo _keyProperty;
|
||||
private static readonly PropertyInfo _loadStateProperty;
|
||||
private static readonly MethodInfo _loadingCoroutineMethod;
|
||||
private readonly object _instance;
|
||||
public static readonly Type Type;
|
||||
|
||||
static EasyBundleHelper()
|
||||
{
|
||||
_ = nameof(IBundleLock.IsLocked);
|
||||
_ = nameof(BindableState.Bind);
|
||||
|
||||
Type = PatchConstants.EftTypes.Single(x => x.GetMethod("set_SameNameAsset", _flags) != null);
|
||||
_pathField = Type.GetField("string_1", _flags);
|
||||
_keyWithoutExtensionField = Type.GetField("string_0", _flags);
|
||||
_bundleLockField = Type.GetFields(_flags).FirstOrDefault(x => x.FieldType == typeof(IBundleLock));
|
||||
_dependencyKeysProperty = Type.GetProperty("DependencyKeys");
|
||||
_keyProperty = Type.GetProperty("Key");
|
||||
_loadStateProperty = Type.GetProperty("LoadState");
|
||||
_loadingCoroutineMethod = Type.GetMethods(_flags).Single(x => x.GetParameters().Length == 0 && x.ReturnType == typeof(Task));
|
||||
}
|
||||
|
||||
public EasyBundleHelper(object easyBundle)
|
||||
{
|
||||
_instance = easyBundle;
|
||||
}
|
||||
|
||||
public IEnumerable<string> DependencyKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
return (IEnumerable<string>)_dependencyKeysProperty.GetValue(_instance);
|
||||
}
|
||||
set
|
||||
{
|
||||
_dependencyKeysProperty.SetValue(_instance, value);
|
||||
}
|
||||
}
|
||||
|
||||
public IBundleLock BundleLock
|
||||
{
|
||||
get
|
||||
{
|
||||
return (IBundleLock)_bundleLockField.GetValue(_instance);
|
||||
}
|
||||
set
|
||||
{
|
||||
_bundleLockField.SetValue(_instance, value);
|
||||
}
|
||||
}
|
||||
|
||||
public string Path
|
||||
{
|
||||
get
|
||||
{
|
||||
return (string)_pathField.GetValue(_instance);
|
||||
}
|
||||
set
|
||||
{
|
||||
_pathField.SetValue(_instance, value);
|
||||
}
|
||||
}
|
||||
|
||||
public string Key
|
||||
{
|
||||
get
|
||||
{
|
||||
return (string)_keyProperty.GetValue(_instance);
|
||||
}
|
||||
set
|
||||
{
|
||||
_keyProperty.SetValue(_instance, value);
|
||||
}
|
||||
}
|
||||
|
||||
public BindableState LoadState
|
||||
{
|
||||
get
|
||||
{
|
||||
return (BindableState)_loadStateProperty.GetValue(_instance);
|
||||
}
|
||||
set
|
||||
{
|
||||
_loadStateProperty.SetValue(_instance, value);
|
||||
}
|
||||
}
|
||||
|
||||
public string KeyWithoutExtension
|
||||
{
|
||||
get
|
||||
{
|
||||
return (string)_keyWithoutExtensionField.GetValue(_instance);
|
||||
}
|
||||
set
|
||||
{
|
||||
_keyWithoutExtensionField.SetValue(_instance, value);
|
||||
}
|
||||
}
|
||||
|
||||
public Task LoadingCoroutine()
|
||||
{
|
||||
return (Task)_loadingCoroutineMethod.Invoke(_instance, new object[] { });
|
||||
}
|
||||
}
|
||||
}
|
35
project/Aki.Debugging/Aki.Debugging.csproj
Normal file
35
project/Aki.Debugging/Aki.Debugging.csproj
Normal file
@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AssemblyName>aki-debugging</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Company>Aki</Company>
|
||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.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.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||
<Reference Include="UnityEngine.InputLegacyModule" HintPath="..\Shared\Managed\UnityEngine.InputLegacyModule.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.UIModule" HintPath="..\Shared\Managed\UnityEngine.UIModule.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
27
project/Aki.Debugging/AkiDebuggingPlugin.cs
Normal file
27
project/Aki.Debugging/AkiDebuggingPlugin.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Aki.Debugging.Patches;
|
||||
using BepInEx;
|
||||
|
||||
namespace Aki.Debugging
|
||||
{
|
||||
[BepInPlugin("com.spt-aki.debugging", "AKI.Debugging", "1.0.0")]
|
||||
public class AkiDebuggingPlugin : BaseUnityPlugin
|
||||
{
|
||||
public AkiDebuggingPlugin()
|
||||
{
|
||||
Logger.LogInfo("Loading: Aki.Debugging");
|
||||
|
||||
try
|
||||
{
|
||||
// new CoordinatesPatch().Enable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"{GetType().Name}: {ex}");
|
||||
throw;
|
||||
}
|
||||
|
||||
Logger.LogInfo("Completed: Aki.Debugging");
|
||||
}
|
||||
}
|
||||
}
|
70
project/Aki.Debugging/Patches/CoordinatesPatch.cs
Normal file
70
project/Aki.Debugging/Patches/CoordinatesPatch.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Debugging.Patches
|
||||
{
|
||||
public class CoordinatesPatch : ModulePatch
|
||||
{
|
||||
private static TextMeshProUGUI _alphaLabel;
|
||||
private static PropertyInfo _playerProperty;
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var localGameBaseType = PatchConstants.LocalGameType.BaseType;
|
||||
_playerProperty = localGameBaseType.GetProperty("PlayerOwner", BindingFlags.Public | BindingFlags.Instance);
|
||||
return localGameBaseType.GetMethod("Update", PatchConstants.PrivateFlags);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static void PatchPrefix(object __instance)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.LeftControl))
|
||||
{
|
||||
if (_alphaLabel == null)
|
||||
{
|
||||
_alphaLabel = GameObject.Find("AlphaLabel").GetComponent<TextMeshProUGUI>();
|
||||
_alphaLabel.color = Color.green;
|
||||
_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}]");
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector3 LookingRaycast(Player player)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (player == null || player.Fireport == null)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
Physics.Linecast(player.Fireport.position, player.Fireport.position - player.Fireport.up * 1000f, out var raycastHit, 331776);
|
||||
return raycastHit.point;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Coordinate Debug raycast failed: {e.Message}");
|
||||
return Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
project/Aki.PrePatch/Aki.PrePatch.csproj
Normal file
30
project/Aki.PrePatch/Aki.PrePatch.csproj
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AssemblyName>aki_PrePatch</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Company>Aki</Company>
|
||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
31
project/Aki.PrePatch/AkiBotsPrePatcher.cs
Normal file
31
project/Aki.PrePatch/AkiBotsPrePatcher.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace Aki.PrePatch
|
||||
{
|
||||
public static class AkiBotsPrePatcher
|
||||
{
|
||||
public static IEnumerable<string> TargetDLLs { get; } = new[] { "Assembly-CSharp.dll" };
|
||||
|
||||
public static long sptUsecValue = 0x80000000;
|
||||
public static long sptBearValue = 0x100000000;
|
||||
|
||||
public static void Patch(ref AssemblyDefinition assembly)
|
||||
{
|
||||
var botEnums = assembly.MainModule.GetType("EFT.WildSpawnType");
|
||||
|
||||
var sptUsec = new FieldDefinition("sptUsec",
|
||||
FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault,
|
||||
botEnums)
|
||||
{ Constant = sptUsecValue };
|
||||
|
||||
var sptBear = new FieldDefinition("sptBear",
|
||||
FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault,
|
||||
botEnums)
|
||||
{ Constant = sptBearValue };
|
||||
|
||||
botEnums.Fields.Add(sptUsec);
|
||||
botEnums.Fields.Add(sptBear);
|
||||
}
|
||||
}
|
||||
}
|
35
project/Aki.Reflection/Aki.Reflection.csproj
Normal file
35
project/Aki.Reflection/Aki.Reflection.csproj
Normal file
@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Company>Aki</Company>
|
||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||
<Reference Include="bsg.componentace.compression.libs.zlib" HintPath="..\Shared\Managed\bsg.componentace.compression.libs.zlib.dll" Private="False" />
|
||||
<Reference Include="Comfort" HintPath="..\Shared\Managed\Comfort.dll" Private="False" />
|
||||
<Reference Include="FilesChecker" HintPath="..\Shared\Managed\FilesChecker.dll" Private="False" />
|
||||
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BepInEx.Analyzers" Version="1.0.8" PrivateAssets="All" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
|
||||
<PackageReference Include="BepInEx.Core" Version="5.4.21" />
|
||||
<PackageReference Include="BepInEx.PluginInfoProps" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
48
project/Aki.Reflection/CodeWrapper/Code.cs
Normal file
48
project/Aki.Reflection/CodeWrapper/Code.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Aki.Reflection.CodeWrapper
|
||||
{
|
||||
public class Code
|
||||
{
|
||||
public OpCode OpCode { get; }
|
||||
public Type CallerType { get; }
|
||||
public object OperandTarget { get; }
|
||||
public Type[] Parameters { get; }
|
||||
public bool HasOperand { get; }
|
||||
|
||||
public Code(OpCode opCode)
|
||||
{
|
||||
OpCode = opCode;
|
||||
HasOperand = false;
|
||||
}
|
||||
|
||||
public Code(OpCode opCode, object operandTarget)
|
||||
{
|
||||
OpCode = opCode;
|
||||
OperandTarget = operandTarget;
|
||||
HasOperand = true;
|
||||
}
|
||||
|
||||
public Code(OpCode opCode, Type callerType)
|
||||
{
|
||||
OpCode = opCode;
|
||||
CallerType = callerType;
|
||||
HasOperand = true;
|
||||
}
|
||||
|
||||
public Code(OpCode opCode, Type callerType, object operandTarget, Type[] parameters = null)
|
||||
{
|
||||
OpCode = opCode;
|
||||
CallerType = callerType;
|
||||
OperandTarget = operandTarget;
|
||||
Parameters = parameters;
|
||||
HasOperand = true;
|
||||
}
|
||||
|
||||
public virtual Label? GetLabel()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
66
project/Aki.Reflection/CodeWrapper/CodeGenerator.cs
Normal file
66
project/Aki.Reflection/CodeWrapper/CodeGenerator.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Aki.Reflection.CodeWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to generate IL code for transpilers
|
||||
/// </summary>
|
||||
public class CodeGenerator
|
||||
{
|
||||
public static List<CodeInstruction> GenerateInstructions(List<Code> codes)
|
||||
{
|
||||
var list = new List<CodeInstruction>();
|
||||
|
||||
foreach (Code code in codes)
|
||||
{
|
||||
list.Add(ParseCode(code));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static CodeInstruction ParseCode(Code code)
|
||||
{
|
||||
if (!code.HasOperand)
|
||||
{
|
||||
return new CodeInstruction(code.OpCode) { labels = GetLabelList(code) };
|
||||
}
|
||||
|
||||
if (code.OpCode == OpCodes.Ldfld || code.OpCode == OpCodes.Stfld)
|
||||
{
|
||||
return new CodeInstruction(code.OpCode, AccessTools.Field(code.CallerType, code.OperandTarget as string)) { labels = GetLabelList(code) };
|
||||
}
|
||||
|
||||
if (code.OpCode == OpCodes.Call || code.OpCode == OpCodes.Callvirt)
|
||||
{
|
||||
return new CodeInstruction(code.OpCode, AccessTools.Method(code.CallerType, code.OperandTarget as string, code.Parameters)) { labels = GetLabelList(code) };
|
||||
}
|
||||
|
||||
if (code.OpCode == OpCodes.Box)
|
||||
{
|
||||
return new CodeInstruction(code.OpCode, code.CallerType) { labels = GetLabelList(code) };
|
||||
}
|
||||
|
||||
if (code.OpCode == OpCodes.Br || code.OpCode == OpCodes.Brfalse || code.OpCode == OpCodes.Brtrue || code.OpCode == OpCodes.Brtrue_S
|
||||
|| code.OpCode == OpCodes.Brfalse_S || code.OpCode == OpCodes.Br_S)
|
||||
{
|
||||
return new CodeInstruction(code.OpCode, code.OperandTarget) { labels = GetLabelList(code) };
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Code with OpCode {nameof(code.OpCode)} is not supported.");
|
||||
}
|
||||
|
||||
private static List<Label> GetLabelList(Code code)
|
||||
{
|
||||
if (code.GetLabel() == null)
|
||||
{
|
||||
return new List<Label>();
|
||||
}
|
||||
|
||||
return new List<Label>() { (Label)code.GetLabel() };
|
||||
}
|
||||
}
|
||||
}
|
35
project/Aki.Reflection/CodeWrapper/CodeWithLabel.cs
Normal file
35
project/Aki.Reflection/CodeWrapper/CodeWithLabel.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Aki.Reflection.CodeWrapper
|
||||
{
|
||||
public class CodeWithLabel : Code
|
||||
{
|
||||
public Label Label { get; }
|
||||
|
||||
public CodeWithLabel(OpCode opCode, Label label) : base(opCode)
|
||||
{
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public CodeWithLabel(OpCode opCode, Label label, object operandTarget) : base(opCode, operandTarget)
|
||||
{
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public CodeWithLabel(OpCode opCode, Label label, Type callerType) : base(opCode, callerType)
|
||||
{
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public CodeWithLabel(OpCode opCode, Label label, Type callerType, object operandTarget, Type[] parameters = null) : base(opCode, callerType, operandTarget, parameters)
|
||||
{
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public override Label? GetLabel()
|
||||
{
|
||||
return Label;
|
||||
}
|
||||
}
|
||||
}
|
35
project/Aki.Reflection/Patching/Attributes.cs
Normal file
35
project/Aki.Reflection/Patching/Attributes.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Aki.Reflection.Patching
|
||||
{
|
||||
[MeansImplicitUse]
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class PatchPrefixAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[MeansImplicitUse]
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class PatchPostfixAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[MeansImplicitUse]
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class PatchTranspilerAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[MeansImplicitUse]
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class PatchFinalizerAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[MeansImplicitUse]
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class PatchILManipulatorAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
150
project/Aki.Reflection/Patching/ModulePatch.cs
Normal file
150
project/Aki.Reflection/Patching/ModulePatch.cs
Normal file
@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using BepInEx.Logging;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Aki.Reflection.Patching
|
||||
{
|
||||
public abstract class ModulePatch
|
||||
{
|
||||
private readonly Harmony _harmony;
|
||||
private readonly List<HarmonyMethod> _prefixList;
|
||||
private readonly List<HarmonyMethod> _postfixList;
|
||||
private readonly List<HarmonyMethod> _transpilerList;
|
||||
private readonly List<HarmonyMethod> _finalizerList;
|
||||
private readonly List<HarmonyMethod> _ilmanipulatorList;
|
||||
|
||||
protected static ManualLogSource Logger { get; private set; }
|
||||
|
||||
protected ModulePatch() : this(null)
|
||||
{
|
||||
if (Logger == null)
|
||||
{
|
||||
Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(ModulePatch));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="name">Name</param>
|
||||
protected ModulePatch(string name = null)
|
||||
{
|
||||
_harmony = new Harmony(name ?? GetType().Name);
|
||||
_prefixList = GetPatchMethods(typeof(PatchPrefixAttribute));
|
||||
_postfixList = GetPatchMethods(typeof(PatchPostfixAttribute));
|
||||
_transpilerList = GetPatchMethods(typeof(PatchTranspilerAttribute));
|
||||
_finalizerList = GetPatchMethods(typeof(PatchFinalizerAttribute));
|
||||
_ilmanipulatorList = GetPatchMethods(typeof(PatchILManipulatorAttribute));
|
||||
|
||||
if (_prefixList.Count == 0
|
||||
&& _postfixList.Count == 0
|
||||
&& _transpilerList.Count == 0
|
||||
&& _finalizerList.Count == 0
|
||||
&& _ilmanipulatorList.Count == 0)
|
||||
{
|
||||
throw new Exception($"{_harmony.Id}: At least one of the patch methods must be specified");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get original method
|
||||
/// </summary>
|
||||
/// <returns>Method</returns>
|
||||
protected abstract MethodBase GetTargetMethod();
|
||||
|
||||
/// <summary>
|
||||
/// Get HarmonyMethod from string
|
||||
/// </summary>
|
||||
/// <param name="attributeType">Attribute type</param>
|
||||
/// <returns>Method</returns>
|
||||
private List<HarmonyMethod> GetPatchMethods(Type attributeType)
|
||||
{
|
||||
var T = GetType();
|
||||
var methods = new List<HarmonyMethod>();
|
||||
|
||||
foreach (var method in T.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly))
|
||||
{
|
||||
if (method.GetCustomAttribute(attributeType) != null)
|
||||
{
|
||||
methods.Add(new HarmonyMethod(method));
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply patch to target
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
var target = GetTargetMethod();
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{_harmony.Id}: TargetMethod is null");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var prefix in _prefixList)
|
||||
{
|
||||
_harmony.Patch(target, prefix: prefix);
|
||||
}
|
||||
|
||||
foreach (var postfix in _postfixList)
|
||||
{
|
||||
_harmony.Patch(target, postfix: postfix);
|
||||
}
|
||||
|
||||
foreach (var transpiler in _transpilerList)
|
||||
{
|
||||
_harmony.Patch(target, transpiler: transpiler);
|
||||
}
|
||||
|
||||
foreach (var finalizer in _finalizerList)
|
||||
{
|
||||
_harmony.Patch(target, finalizer: finalizer);
|
||||
}
|
||||
|
||||
foreach (var ilmanipulator in _ilmanipulatorList)
|
||||
{
|
||||
_harmony.Patch(target, ilmanipulator: ilmanipulator);
|
||||
}
|
||||
|
||||
Logger.LogInfo($"Enabled patch {_harmony.Id}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"{_harmony.Id}: {ex}");
|
||||
throw new Exception($"{_harmony.Id}:", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove applied patch from target
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
var target = GetTargetMethod();
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{_harmony.Id}: TargetMethod is null");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_harmony.Unpatch(target, HarmonyPatchType.All, _harmony.Id);
|
||||
Logger.LogInfo($"Disabled patch {_harmony.Id}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"{_harmony.Id}: {ex}");
|
||||
throw new Exception($"{_harmony.Id}:", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
project/Aki.Reflection/Utils/ClientAppUtils.cs
Normal file
18
project/Aki.Reflection/Utils/ClientAppUtils.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
|
||||
namespace Aki.Reflection.Utils
|
||||
{
|
||||
public static class ClientAppUtils
|
||||
{
|
||||
public static ClientApplication<ISession> GetClientApp()
|
||||
{
|
||||
return Singleton<ClientApplication<ISession>>.Instance;
|
||||
}
|
||||
|
||||
public static TarkovApplication GetMainApp()
|
||||
{
|
||||
return GetClientApp() as TarkovApplication;
|
||||
}
|
||||
}
|
||||
}
|
28
project/Aki.Reflection/Utils/HookObject.cs
Normal file
28
project/Aki.Reflection/Utils/HookObject.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Aki.Reflection.Utils
|
||||
{
|
||||
public static class HookObject
|
||||
{
|
||||
public static GameObject _object
|
||||
{
|
||||
get
|
||||
{
|
||||
GameObject result = GameObject.Find("Aki.Hook");
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
result = new GameObject("Aki.Hook");
|
||||
Object.DontDestroyOnLoad(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static T AddOrGetComponent<T>() where T : MonoBehaviour
|
||||
{
|
||||
return _object.GetOrAddComponent<T>();
|
||||
}
|
||||
}
|
||||
}
|
49
project/Aki.Reflection/Utils/PatchConstants.cs
Normal file
49
project/Aki.Reflection/Utils/PatchConstants.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using FilesChecker;
|
||||
|
||||
namespace Aki.Reflection.Utils
|
||||
{
|
||||
public static class PatchConstants
|
||||
{
|
||||
public static BindingFlags PrivateFlags { get; private set; }
|
||||
public static BindingFlags PublicFlags { get; private set; }
|
||||
public static Type[] EftTypes { get; private set; }
|
||||
public static Type[] FilesCheckerTypes { get; private set; }
|
||||
public static Type LocalGameType { get; private set; }
|
||||
public static Type ExfilPointManagerType { get; private set; }
|
||||
public static Type SessionInterfaceType { get; private set; }
|
||||
public static Type BackendSessionInterfaceType { get; private set; }
|
||||
|
||||
private static ISession _backEndSession;
|
||||
public static ISession BackEndSession
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_backEndSession == null)
|
||||
{
|
||||
_backEndSession = Singleton<ClientApplication<ISession>>.Instance.GetClientBackEndSession();
|
||||
}
|
||||
|
||||
return _backEndSession;
|
||||
}
|
||||
}
|
||||
|
||||
static PatchConstants()
|
||||
{
|
||||
_ = nameof(ISession.GetPhpSessionId);
|
||||
|
||||
PrivateFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
|
||||
PublicFlags = BindingFlags.Public | BindingFlags.Instance;
|
||||
EftTypes = typeof(AbstractGame).Assembly.GetTypes();
|
||||
FilesCheckerTypes = typeof(ICheckResult).Assembly.GetTypes();
|
||||
LocalGameType = EftTypes.Single(x => x.Name == "LocalGame");
|
||||
ExfilPointManagerType = EftTypes.Single(x => x.GetMethod("InitAllExfiltrationPoints") != null);
|
||||
SessionInterfaceType = EftTypes.Single(x => x.GetMethods().Select(y => y.Name).Contains("GetPhpSessionId") && x.IsInterface);
|
||||
BackendSessionInterfaceType = EftTypes.Single(x => x.GetMethods().Select(y => y.Name).Contains("ChangeProfileStatus") && x.IsInterface);
|
||||
}
|
||||
}
|
||||
}
|
35
project/Aki.SinglePlayer/Aki.SinglePlayer.csproj
Normal file
35
project/Aki.SinglePlayer/Aki.SinglePlayer.csproj
Normal file
@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AssemblyName>aki-singleplayer</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Company>Aki</Company>
|
||||
<Copyright>Copyright @ Aki 2022</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\Assembly-CSharp.dll" Private="False" />
|
||||
<Reference Include="Comfort.Unity" HintPath="..\Shared\Managed\Comfort.Unity.dll" Private="False" />
|
||||
<Reference Include="DissonanceVoip" HintPath="..\Shared\Managed\DissonanceVoip.dll" Private="False" />
|
||||
<Reference Include="ItemComponent.Types" HintPath="..\Shared\Managed\ItemComponent.Types.dll" Private="False" />
|
||||
<Reference Include="Comfort" HintPath="..\Shared\Managed\Comfort.dll" Private="False" />
|
||||
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Aki.Common\Aki.Common.csproj" />
|
||||
<ProjectReference Include="..\Aki.Reflection\Aki.Reflection.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
59
project/Aki.SinglePlayer/AkiSingleplayerPlugin.cs
Normal file
59
project/Aki.SinglePlayer/AkiSingleplayerPlugin.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Aki.SinglePlayer.Patches.Healing;
|
||||
using Aki.SinglePlayer.Patches.MainMenu;
|
||||
using Aki.SinglePlayer.Patches.Progression;
|
||||
using Aki.SinglePlayer.Patches.Quests;
|
||||
using Aki.SinglePlayer.Patches.RaidFix;
|
||||
using Aki.SinglePlayer.Patches.ScavMode;
|
||||
using BepInEx;
|
||||
|
||||
namespace Aki.SinglePlayer
|
||||
{
|
||||
[BepInPlugin("com.spt-aki.singleplayer", "AKI.Singleplayer", "1.0.0")]
|
||||
class AkiSingleplayerPlugin : BaseUnityPlugin
|
||||
{
|
||||
public AkiSingleplayerPlugin()
|
||||
{
|
||||
Logger.LogInfo("Loading: Aki.SinglePlayer");
|
||||
|
||||
try
|
||||
{
|
||||
new OfflineSaveProfilePatch().Enable();
|
||||
new OfflineSpawnPointPatch().Enable();
|
||||
new ExperienceGainPatch().Enable();
|
||||
new MainMenuControllerPatch().Enable();
|
||||
new PlayerPatch().Enable();
|
||||
new SelectLocationScreenPatch().Enable();
|
||||
new InsuranceScreenPatch().Enable();
|
||||
new BotTemplateLimitPatch().Enable();
|
||||
new GetNewBotTemplatesPatch().Enable();
|
||||
new RemoveUsedBotProfilePatch().Enable();
|
||||
new DogtagPatch().Enable();
|
||||
new LoadOfflineRaidScreenPatch().Enable();
|
||||
new ScavPrefabLoadPatch().Enable();
|
||||
new ScavProfileLoadPatch().Enable();
|
||||
new ScavExfilPatch().Enable();
|
||||
new ExfilPointManagerPatch().Enable();
|
||||
new TinnitusFixPatch().Enable();
|
||||
new MaxBotPatch().Enable();
|
||||
new SpawnPmcPatch().Enable();
|
||||
new PostRaidHealingPricePatch().Enable();
|
||||
new EndByTimerPatch().Enable();
|
||||
new PostRaidHealScreenPatch().Enable();
|
||||
new VoIPTogglerPatch().Enable();
|
||||
new MidRaidQuestChangePatch().Enable();
|
||||
new HealthControllerPatch().Enable();
|
||||
new LighthouseBridgePatch().Enable();
|
||||
new LighthouseTransmitterPatch().Enable();
|
||||
new EmptyInfilFixPatch().Enable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"{GetType().Name}: {ex}");
|
||||
throw;
|
||||
}
|
||||
|
||||
Logger.LogInfo("Completed: Aki.SinglePlayer");
|
||||
}
|
||||
}
|
||||
}
|
48
project/Aki.SinglePlayer/Models/Healing/BodyPartHealth.cs
Normal file
48
project/Aki.SinglePlayer/Models/Healing/BodyPartHealth.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Aki.SinglePlayer.Models.Healing
|
||||
{
|
||||
public class BodyPartHealth
|
||||
{
|
||||
private readonly Dictionary<EBodyPartEffect, float> _effects = new Dictionary<EBodyPartEffect, float>();
|
||||
public float Maximum { get; private set; }
|
||||
public float Current { get; private set; }
|
||||
|
||||
public IReadOnlyDictionary<EBodyPartEffect, float> Effects => _effects;
|
||||
|
||||
public void Initialize(float current, float maximum)
|
||||
{
|
||||
Maximum = maximum;
|
||||
Current = current;
|
||||
}
|
||||
|
||||
public void ChangeHealth(float diff)
|
||||
{
|
||||
Current += diff;
|
||||
}
|
||||
|
||||
public void AddEffect(EBodyPartEffect bodyPartEffect, float time = -1)
|
||||
{
|
||||
_effects[bodyPartEffect] = time;
|
||||
}
|
||||
|
||||
public void UpdateEffect(EBodyPartEffect bodyPartEffect, float time)
|
||||
{
|
||||
_effects[bodyPartEffect] = time;
|
||||
}
|
||||
|
||||
|
||||
public void RemoveAllEffects()
|
||||
{
|
||||
_effects.Clear();
|
||||
}
|
||||
|
||||
public void RemoveEffect(EBodyPartEffect bodyPartEffect)
|
||||
{
|
||||
if (_effects.ContainsKey(bodyPartEffect))
|
||||
{
|
||||
_effects.Remove(bodyPartEffect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
project/Aki.SinglePlayer/Models/Healing/EBodyPartEffect.cs
Normal file
12
project/Aki.SinglePlayer/Models/Healing/EBodyPartEffect.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Aki.SinglePlayer.Models.Healing
|
||||
{
|
||||
public enum EBodyPartEffect
|
||||
{
|
||||
Fracture,
|
||||
LightBleeding,
|
||||
HeavyBleeding,
|
||||
MildMusclePain,
|
||||
SevereMusclePain,
|
||||
Unknown
|
||||
}
|
||||
}
|
29
project/Aki.SinglePlayer/Models/Healing/PlayerHealth.cs
Normal file
29
project/Aki.SinglePlayer/Models/Healing/PlayerHealth.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Aki.SinglePlayer.Models.Healing
|
||||
{
|
||||
public class PlayerHealth
|
||||
{
|
||||
private readonly Dictionary<EBodyPart, BodyPartHealth> _health;
|
||||
public IReadOnlyDictionary<EBodyPart, BodyPartHealth> Health => _health;
|
||||
public bool IsAlive { get; set; }
|
||||
public float Hydration { get; set; }
|
||||
public float Energy { get; set; }
|
||||
public float Temperature { get; set; }
|
||||
|
||||
public PlayerHealth()
|
||||
{
|
||||
IsAlive = true;
|
||||
_health = new Dictionary<EBodyPart, BodyPartHealth>()
|
||||
{
|
||||
{ EBodyPart.Head, new BodyPartHealth() },
|
||||
{ EBodyPart.Chest, new BodyPartHealth() },
|
||||
{ EBodyPart.Stomach, new BodyPartHealth() },
|
||||
{ EBodyPart.LeftArm, new BodyPartHealth() },
|
||||
{ EBodyPart.RightArm, new BodyPartHealth() },
|
||||
{ EBodyPart.LeftLeg, new BodyPartHealth() },
|
||||
{ EBodyPart.RightLeg, new BodyPartHealth() }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Aki.SinglePlayer.Models.Progression
|
||||
{
|
||||
public class LighthouseProgressionClass : MonoBehaviour
|
||||
{
|
||||
private bool _isScav;
|
||||
private GameWorld _gameWorld;
|
||||
private float _timer;
|
||||
private bool _addedToEnemy;
|
||||
private List<MineDirectionalColliders> _mines;
|
||||
private RecodableItemClass _transmitter;
|
||||
private List<Player> _bosses;
|
||||
private bool _aggressor;
|
||||
private bool _disabledDoor;
|
||||
private readonly string _transmitterId = "62e910aaf957f2915e0a5e36";
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_gameWorld = Singleton<GameWorld>.Instance;
|
||||
_bosses = new List<Player>();
|
||||
_mines = GameObject.FindObjectsOfType<MineDirectionalColliders>().ToList();
|
||||
|
||||
if (_gameWorld == null || _gameWorld.MainPlayer.Location.ToLower() != "lighthouse") return;
|
||||
|
||||
// if player is a scav, there is no need to continue this method.
|
||||
if (_gameWorld.MainPlayer.Side == EPlayerSide.Savage)
|
||||
{
|
||||
_isScav = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the players Transmitter.
|
||||
_transmitter = (RecodableItemClass) _gameWorld.MainPlayer.Profile.Inventory.AllRealPlayerItems.FirstOrDefault(x => x.TemplateId == _transmitterId);
|
||||
|
||||
if (_transmitter != null)
|
||||
{
|
||||
GameObject.Find("Attack").SetActive(false);
|
||||
|
||||
// this zone was added in a newer version and the gameObject actually has a \
|
||||
GameObject.Find("CloseZone\\").SetActive(false);
|
||||
|
||||
// Give access to the Lightkeepers door.
|
||||
_gameWorld.BufferZoneController.SetPlayerAccessStatus(_gameWorld.MainPlayer.ProfileId, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_gameWorld == null || _addedToEnemy || _disabledDoor || _transmitter == null) return;
|
||||
|
||||
_timer += Time.deltaTime;
|
||||
|
||||
if (_timer < 10f) return;
|
||||
|
||||
if (_bosses.Count == 0)
|
||||
{
|
||||
SetupBosses();
|
||||
}
|
||||
|
||||
if (_isScav)
|
||||
{
|
||||
PlayerIsScav();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gameWorld?.MainPlayer?.HandsController?.Item?.TemplateId == _transmitterId)
|
||||
{
|
||||
if (_transmitter?.RecodableComponent?.Status == RadioTransmitterStatus.Green)
|
||||
{
|
||||
foreach (var mine in _mines)
|
||||
{
|
||||
if (mine.gameObject.activeSelf)
|
||||
{
|
||||
mine.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var mine in _mines)
|
||||
{
|
||||
if (!mine.gameObject.activeSelf)
|
||||
{
|
||||
mine.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_aggressor)
|
||||
{
|
||||
PlayerIsAggressor();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupBosses()
|
||||
{
|
||||
foreach (var player in _gameWorld.AllPlayers)
|
||||
{
|
||||
if (!player.IsYourPlayer)
|
||||
{
|
||||
if (player.AIData.BotOwner.IsRole(WildSpawnType.bossZryachiy) || player.AIData.BotOwner.IsRole(WildSpawnType.followerZryachiy))
|
||||
{
|
||||
// Sub to Bosses OnDeath event, Set mainplayer to aggressor on this script
|
||||
player.OnPlayerDeadOrUnspawn += player1 =>
|
||||
{
|
||||
if (player1.KillerId != null && player1.KillerId == _gameWorld.MainPlayer.ProfileId)
|
||||
{
|
||||
_aggressor = true;
|
||||
}
|
||||
};
|
||||
|
||||
_bosses.Add(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerIsScav()
|
||||
{
|
||||
// If player is a scav, they must be added to the bosses enemy list otherwise they wont kill them
|
||||
foreach (var boss in _bosses)
|
||||
{
|
||||
boss.AIData.BotOwner.BotsGroup.AddEnemy(_gameWorld.MainPlayer);
|
||||
}
|
||||
|
||||
_addedToEnemy = true;
|
||||
}
|
||||
|
||||
private void PlayerIsAggressor()
|
||||
{
|
||||
// Disable access to Lightkeepers door for the player
|
||||
_gameWorld.BufferZoneController.SetPlayerAccessStatus(_gameWorld.MainPlayer.ProfileId, false);
|
||||
_transmitter?.RecodableComponent?.SetStatus(RadioTransmitterStatus.Yellow);
|
||||
_transmitter?.RecodableComponent?.SetEncoded(false);
|
||||
_disabledDoor = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
using Aki.SinglePlayer.Models.Healing;
|
||||
using Newtonsoft.Json;
|
||||
using EFT;
|
||||
|
||||
namespace Aki.SinglePlayer.Models.Progression
|
||||
{
|
||||
public class SaveProfileRequest
|
||||
{
|
||||
[JsonProperty("exit")]
|
||||
public string Exit;
|
||||
|
||||
[JsonProperty("profile")]
|
||||
public Profile Profile;
|
||||
|
||||
[JsonProperty("isPlayerScav")]
|
||||
public bool IsPlayerScav;
|
||||
|
||||
[JsonProperty("health")]
|
||||
public PlayerHealth Health;
|
||||
|
||||
public SaveProfileRequest()
|
||||
{
|
||||
Exit = "left";
|
||||
}
|
||||
}
|
||||
}
|
40
project/Aki.SinglePlayer/Models/RaidFix/BundleLoader.cs
Normal file
40
project/Aki.SinglePlayer/Models/RaidFix/BundleLoader.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
|
||||
namespace Aki.SinglePlayer.Models.RaidFix
|
||||
{
|
||||
public struct BundleLoader
|
||||
{
|
||||
Profile Profile;
|
||||
TaskScheduler TaskScheduler { get; }
|
||||
|
||||
public BundleLoader(TaskScheduler taskScheduler)
|
||||
{
|
||||
Profile = null;
|
||||
TaskScheduler = taskScheduler;
|
||||
}
|
||||
|
||||
public Task<Profile> LoadBundles(Task<Profile> task)
|
||||
{
|
||||
Profile = task.Result;
|
||||
|
||||
var loadTask = Singleton<PoolManager>.Instance.LoadBundlesAndCreatePools(
|
||||
PoolManager.PoolsCategory.Raid,
|
||||
PoolManager.AssemblyType.Local,
|
||||
Profile.GetAllPrefabPaths(false).ToArray(),
|
||||
JobPriority.General,
|
||||
null,
|
||||
default(CancellationToken));
|
||||
|
||||
return loadTask.ContinueWith(GetProfile, TaskScheduler);
|
||||
}
|
||||
|
||||
private Profile GetProfile(Task task)
|
||||
{
|
||||
return Profile;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Healing
|
||||
{
|
||||
/// <summary>
|
||||
/// HealthController used by post-raid heal screen and health listenen class are different, this patch
|
||||
/// ensures effects (fracture/bleeding) on body parts stay in sync
|
||||
/// </summary>
|
||||
public class HealthControllerPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(HealthControllerClass).GetMethod("ApplyTreatment", BindingFlags.Public | BindingFlags.Instance);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static void PatchPrefix(object healthObserver)
|
||||
{
|
||||
var property = healthObserver.GetType().GetProperty("Effects");
|
||||
if (property != null)
|
||||
{
|
||||
var effects = property.GetValue(healthObserver);
|
||||
if (effects != null && effects.GetType().GetGenericTypeDefinition() == typeof(List<>))
|
||||
{
|
||||
var parsedEffects = ((IList)effects).Cast<object>().ToList();
|
||||
parsedEffects.ForEach(effect =>
|
||||
{
|
||||
var parsedEffect = effect as IEffect;
|
||||
try
|
||||
{
|
||||
// I tried using reflections to raise the event, I also tried replacing the effect
|
||||
// health controller using reflections but they are different types than the one the actual
|
||||
// player class uses so it cant be replaced.
|
||||
// Unfortunately, the easiest way to deal with this is just manually triggering the HealthListener method
|
||||
Utils.Healing.HealthListener.Instance.OnEffectRemovedEvent(parsedEffect);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Exception!\n{ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug("No effects found to heal on the observer!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Healing
|
||||
{
|
||||
public class MainMenuControllerPatch : ModulePatch
|
||||
{
|
||||
static MainMenuControllerPatch()
|
||||
{
|
||||
_ = nameof(IHealthController.HydrationChangedEvent);
|
||||
_ = nameof(MainMenuController.HealthController);
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = typeof(MainMenuController);
|
||||
var desiredMethod = desiredType.GetMethod("method_1", PatchConstants.PrivateFlags);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix(MainMenuController __instance)
|
||||
{
|
||||
var healthController = __instance.HealthController;
|
||||
var listener = Utils.Healing.HealthListener.Instance;
|
||||
|
||||
if (healthController == null)
|
||||
{
|
||||
Logger.LogInfo("MainMenuControllerPatch() - healthController is null");
|
||||
}
|
||||
|
||||
if (listener == null)
|
||||
{
|
||||
Logger.LogInfo("MainMenuControllerPatch() - listener is null");
|
||||
}
|
||||
|
||||
if (healthController != null && listener != null)
|
||||
{
|
||||
listener.Init(healthController, false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
40
project/Aki.SinglePlayer/Patches/Healing/PlayerPatch.cs
Normal file
40
project/Aki.SinglePlayer/Patches/Healing/PlayerPatch.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Healing
|
||||
{
|
||||
public class PlayerPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = typeof(Player);
|
||||
var desiredMethod = desiredType.GetMethod("Init", PatchConstants.PrivateFlags);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static async void PatchPostfix(Task __result, Player __instance, Profile profile)
|
||||
{
|
||||
await __result;
|
||||
|
||||
if (profile?.Id.StartsWith("pmc") == true)
|
||||
{
|
||||
Logger.LogDebug($"Hooking up health listener to profile: {profile.Id}");
|
||||
var listener = Utils.Healing.HealthListener.Instance;
|
||||
listener.Init(__instance.HealthController, true);
|
||||
Logger.LogDebug($"HealthController instance: {__instance.HealthController.GetHashCode()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug($"Skipped on HealthController instance: {__instance.HealthController.GetHashCode()} for profile id: {profile?.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Healing
|
||||
{
|
||||
/// <summary>
|
||||
/// We need to alter Class1049.smethod_0().
|
||||
/// Set the passed in ERaidMode to online, this ensures the heal screen shows.
|
||||
/// It cannot be changed in the calling method as doing so causes the post-raid exp display to remain at 0
|
||||
/// </summary>
|
||||
public class PostRaidHealScreenPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
// Class1049.smethod_0 as of 18969
|
||||
//internal static Class1049 smethod_0(GInterface29 backend, string profileId, Profile savageProfile, LocationSettingsClass.GClass1097 location, ExitStatus exitStatus, TimeSpan exitTime, ERaidMode raidMode)
|
||||
var desiredType = PatchConstants.EftTypes.Single(x => x.Name == "PostRaidHealthScreenClass");
|
||||
var desiredMethod = desiredType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic).Single(IsTargetMethod);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
private static bool IsTargetMethod(MethodInfo mi)
|
||||
{
|
||||
var parameters = mi.GetParameters();
|
||||
return parameters.Length == 7
|
||||
&& parameters[0].Name == "session"
|
||||
&& parameters[1].Name == "profileId"
|
||||
&& parameters[2].Name == "savageProfile"
|
||||
&& parameters[3].Name == "location"
|
||||
&& parameters[4].Name == "exitStatus"
|
||||
&& parameters[5].Name == "exitTime"
|
||||
&& parameters[6].Name == "raidMode";
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(TarkovApplication __instance, ref ERaidMode raidMode)
|
||||
{
|
||||
raidMode = ERaidMode.Online;
|
||||
|
||||
return true; // Perform original method
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using EFT;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.MainMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Force ERaidMode to online to make interface show insurance page
|
||||
/// </summary>
|
||||
class InsuranceScreenPatch : ModulePatch
|
||||
{
|
||||
static InsuranceScreenPatch()
|
||||
{
|
||||
_ = nameof(MainMenuController.InventoryController);
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = typeof(MainMenuController);
|
||||
var desiredMethod = desiredType.GetMethod("method_66", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static void PrefixPatch(RaidSettings ___raidSettings_0)
|
||||
{
|
||||
___raidSettings_0.RaidMode = ERaidMode.Online;
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PostfixPatch(RaidSettings ___raidSettings_0)
|
||||
{
|
||||
___raidSettings_0.RaidMode = ERaidMode.Local;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT.UI;
|
||||
using EFT.UI.Matchmaker;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.MainMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Remove the ready button from select location screen
|
||||
/// </summary>
|
||||
public class SelectLocationScreenPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = typeof(MatchMakerSelectionLocationScreen);
|
||||
var desiredMethod = desiredType.GetMethod("Awake", PatchConstants.PrivateFlags);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix(DefaultUIButton ____readyButton)
|
||||
{
|
||||
____readyButton.Interactable = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT.UI.SessionEnd;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Progression
|
||||
{
|
||||
public class ExperienceGainPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = typeof(SessionResultExperienceCount);
|
||||
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags).FirstOrDefault(IsTargetMethod);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
private static bool IsTargetMethod(MethodInfo mi)
|
||||
{
|
||||
var parameters = mi.GetParameters();
|
||||
return (parameters.Length == 3
|
||||
&& parameters[0].Name == "profile"
|
||||
&& parameters[1].Name == "isOnline"
|
||||
&& parameters[2].Name == "exitStatus"
|
||||
&& parameters[1].ParameterType == typeof(bool));
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static void PatchPrefix(ref bool isOnline)
|
||||
{
|
||||
isOnline = false;
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix(ref bool isOnline)
|
||||
{
|
||||
isOnline = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.SinglePlayer.Models.Progression;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Progression
|
||||
{
|
||||
public class LighthouseBridgePatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(GameWorld).GetMethod(nameof(GameWorld.OnGameStarted));
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix()
|
||||
{
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
|
||||
if (gameWorld == null || gameWorld.MainPlayer.Location.ToLower() != "lighthouse") return;
|
||||
|
||||
gameWorld.GetOrAddComponent<LighthouseProgressionClass>();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Progression
|
||||
{
|
||||
public class LighthouseTransmitterPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(RadioTransmitterHandlerClass).GetMethod("method_4", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(RadioTransmitterHandlerClass __instance)
|
||||
{
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
|
||||
if (gameWorld == null) return false;
|
||||
|
||||
var transmitter = __instance.RecodableComponent;
|
||||
|
||||
if (transmitter.IsEncoded)
|
||||
{
|
||||
transmitter.SetStatus(RadioTransmitterStatus.Green);
|
||||
}
|
||||
else if (gameWorld.MainPlayer.IsAgressorInLighthouseTraderZone)
|
||||
{
|
||||
// this might need to be tested and changed as I don't think this currently is affect upon killing bosses
|
||||
transmitter.SetStatus(RadioTransmitterStatus.Yellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
transmitter.SetStatus(RadioTransmitterStatus.Red);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using HarmonyLib;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Progression
|
||||
{
|
||||
/// <summary>
|
||||
/// After picking up a quest item, trigger CheckForStatusChange() from the questController to fully update a quest subtasks to show (e.g. `survive and extract item from raid` task)
|
||||
/// </summary>
|
||||
public class MidRaidQuestChangePatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(Profile).GetMethod("AddToCarriedQuestItems", BindingFlags.Public | BindingFlags.Instance);
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix()
|
||||
{
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
|
||||
if (gameWorld != null)
|
||||
{
|
||||
var player = gameWorld.MainPlayer;
|
||||
|
||||
var questController = Traverse.Create(player).Field<QuestControllerClass>("_questController").Value;
|
||||
if (questController != null)
|
||||
{
|
||||
foreach (var quest in questController.Quests.ToList())
|
||||
{
|
||||
quest.CheckForStatusChange(true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Aki.SinglePlayer.Models.Progression;
|
||||
using Aki.SinglePlayer.Utils.Progression;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using HarmonyLib;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Progression
|
||||
{
|
||||
public class OfflineSaveProfilePatch : ModulePatch
|
||||
{
|
||||
private static readonly JsonConverter[] _defaultJsonConverters;
|
||||
|
||||
static OfflineSaveProfilePatch()
|
||||
{
|
||||
_ = nameof(ClientMetrics.Metrics);
|
||||
|
||||
var converterClass = typeof(AbstractGame).Assembly.GetTypes()
|
||||
.First(t => t.GetField("Converters", BindingFlags.Static | BindingFlags.Public) != null);
|
||||
|
||||
_defaultJsonConverters = Traverse.Create(converterClass).Field<JsonConverter[]>("Converters").Value;
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
// method_45 - as of 16432
|
||||
// method_43 - as of 18876
|
||||
var desiredType = PatchConstants.EftTypes.Single(x => x.Name == "TarkovApplication");
|
||||
var desiredMethod = Array.Find(desiredType.GetMethods(PatchConstants.PrivateFlags), IsTargetMethod);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
private bool IsTargetMethod(MethodInfo arg)
|
||||
{
|
||||
var parameters = arg.GetParameters();
|
||||
return parameters.Length > 4
|
||||
&& parameters[0]?.Name == "profileId"
|
||||
&& parameters[1]?.Name == "savageProfile"
|
||||
&& parameters[2]?.Name == "location"
|
||||
&& arg.ReturnType == typeof(void);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static void PatchPrefix(string profileId, RaidSettings ____raidSettings, TarkovApplication __instance, Result<ExitStatus, TimeSpan, ClientMetrics> result)
|
||||
{
|
||||
// Get scav or pmc profile based on IsScav value
|
||||
var profile = (____raidSettings.IsScav)
|
||||
? PatchConstants.BackEndSession.ProfileOfPet
|
||||
: PatchConstants.BackEndSession.Profile;
|
||||
|
||||
SaveProfileRequest request = new SaveProfileRequest
|
||||
{
|
||||
Exit = result.Value0.ToString().ToLowerInvariant(),
|
||||
Profile = profile,
|
||||
Health = Utils.Healing.HealthListener.Instance.CurrentHealth,
|
||||
IsPlayerScav = ____raidSettings.IsScav
|
||||
};
|
||||
|
||||
RequestHandler.PutJson("/raid/profile/save", request.ToJson(_defaultJsonConverters.AddItem(new NotesJsonConverter()).ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using EFT.Game.Spawning;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Progression
|
||||
{
|
||||
public class OfflineSpawnPointPatch : ModulePatch
|
||||
{
|
||||
static OfflineSpawnPointPatch()
|
||||
{
|
||||
_ = nameof(ISpawnPoints.CreateSpawnPoint);
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = PatchConstants.EftTypes.First(IsTargetType);
|
||||
var desiredMethod = desiredType
|
||||
.GetMethods(PatchConstants.PrivateFlags)
|
||||
.First(m => m.Name.Contains("SelectSpawnPoint"));
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
private static bool IsTargetType(Type type)
|
||||
{
|
||||
// GClass1812 as of 17349
|
||||
// GClass1886 as of 18876
|
||||
return (type.GetMethods(PatchConstants.PrivateFlags).Any(x => x.Name.IndexOf("CheckFarthestFromOtherPlayers", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
&& type.IsClass);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(
|
||||
ref ISpawnPoint __result,
|
||||
object __instance,
|
||||
ESpawnCategory category,
|
||||
EPlayerSide side,
|
||||
string groupId,
|
||||
IAIDetails person,
|
||||
string infiltration)
|
||||
{
|
||||
var spawnPointsField = (ISpawnPoints)__instance.GetType().GetFields(PatchConstants.PrivateFlags).SingleOrDefault(f => f.FieldType == typeof(ISpawnPoints))?.GetValue(__instance);
|
||||
if (spawnPointsField == null)
|
||||
{
|
||||
throw new Exception($"OfflineSpawnPointPatch: Failed to locate field of {nameof(ISpawnPoints)} on class instance ({__instance.GetType().Name})");
|
||||
}
|
||||
|
||||
var mapSpawnPoints = spawnPointsField.ToList();
|
||||
var unfilteredFilteredSpawnPoints = mapSpawnPoints.ToList();
|
||||
|
||||
// filter by e.g. 'Boiler Tanks' (always seems to be map name?)
|
||||
if (!string.IsNullOrEmpty(infiltration))
|
||||
{
|
||||
mapSpawnPoints = mapSpawnPoints.Where(sp => sp?.Infiltration != null && (string.IsNullOrEmpty(infiltration) || sp.Infiltration.Equals(infiltration))).ToList();
|
||||
}
|
||||
|
||||
mapSpawnPoints = FilterByPlayerSide(mapSpawnPoints, category, side);
|
||||
|
||||
__result = mapSpawnPoints.Count == 0
|
||||
? GetFallBackSpawnPoint(unfilteredFilteredSpawnPoints, category, side, infiltration)
|
||||
: mapSpawnPoints.RandomElement();
|
||||
|
||||
Logger.LogInfo($"Desired spawnpoint: [{category}] [{side}] [{infiltration}]");
|
||||
Logger.LogInfo($"PatchPrefix SelectSpawnPoint: [{__result.Id}] [{__result.Name}] [{__result.Categories}] [{__result.Sides}] [{__result.Infiltration}]");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<ISpawnPoint> FilterByPlayerSide(List<ISpawnPoint> mapSpawnPoints, ESpawnCategory category, EPlayerSide side)
|
||||
{
|
||||
// Filter by category 'player' and by side ('usec', 'bear')
|
||||
return mapSpawnPoints.Where(sp => sp.Categories.Contain(category) && sp.Sides.Contain(side)).ToList();
|
||||
}
|
||||
|
||||
private static ISpawnPoint GetFallBackSpawnPoint(List<ISpawnPoint> spawnPoints, ESpawnCategory category, EPlayerSide side, string infiltration)
|
||||
{
|
||||
Logger.LogWarning($"PatchPrefix SelectSpawnPoint: Couldn't find any spawn points for: {category} | {side} | {infiltration} using random filtered spawn instead");
|
||||
return spawnPoints.Where(sp => sp.Categories.Contain(ESpawnCategory.Player)).RandomElement();
|
||||
}
|
||||
}
|
||||
}
|
73
project/Aki.SinglePlayer/Patches/Quests/DogtagPatch.cs
Normal file
73
project/Aki.SinglePlayer/Patches/Quests/DogtagPatch.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using EFT;
|
||||
using EFT.InventoryLogic;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Quests
|
||||
{
|
||||
public class DogtagPatch : ModulePatch
|
||||
{
|
||||
private static BindingFlags _flags;
|
||||
private static PropertyInfo _getEquipmentProperty;
|
||||
|
||||
static DogtagPatch()
|
||||
{
|
||||
_ = nameof(EquipmentClass.GetSlot);
|
||||
_ = nameof(DamageInfo.Weapon);
|
||||
|
||||
_flags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
_getEquipmentProperty = typeof(Player).GetProperty("Equipment", _flags);
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(Player).GetMethod("OnBeenKilledByAggressor", _flags);
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix(Player __instance, Player aggressor, DamageInfo damageInfo)
|
||||
{
|
||||
if (__instance.Profile.Info.Side == EPlayerSide.Savage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var equipment = (EquipmentClass)_getEquipmentProperty.GetValue(__instance);
|
||||
var dogtagSlot = equipment.GetSlot(EquipmentSlot.Dogtag);
|
||||
var dogtagItem = dogtagSlot.ContainedItem;
|
||||
|
||||
if (dogtagItem == null)
|
||||
{
|
||||
Logger.LogError("DogtagPatch error > DogTag slot item is null somehow.");
|
||||
return;
|
||||
}
|
||||
|
||||
var itemComponent = dogtagItem.GetItemComponent<DogtagComponent>();
|
||||
|
||||
if (itemComponent == null)
|
||||
{
|
||||
Logger.LogError("DogtagPatch error > DogTagComponent on dog tag slot is null. Something went horrifically wrong!");
|
||||
return;
|
||||
}
|
||||
|
||||
var victimProfileInfo = __instance.Profile.Info;
|
||||
|
||||
itemComponent.AccountId = __instance.Profile.AccountId;
|
||||
itemComponent.ProfileId = __instance.Profile.Id;
|
||||
itemComponent.Nickname = victimProfileInfo.Nickname;
|
||||
itemComponent.Side = victimProfileInfo.Side;
|
||||
itemComponent.KillerName = aggressor.Profile.Info.Nickname;
|
||||
itemComponent.Time = DateTime.Now;
|
||||
itemComponent.Status = "Killed by ";
|
||||
itemComponent.KillerAccountId = aggressor.Profile.AccountId;
|
||||
itemComponent.KillerProfileId = aggressor.Profile.Id;
|
||||
itemComponent.WeaponName = damageInfo.Weapon.Name;
|
||||
|
||||
if (__instance.Profile.Info.Experience > 0)
|
||||
{
|
||||
itemComponent.Level = victimProfileInfo.Level;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
project/Aki.SinglePlayer/Patches/Quests/EndByTimerPatch.cs
Normal file
55
project/Aki.SinglePlayer/Patches/Quests/EndByTimerPatch.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Quests
|
||||
{
|
||||
/// <summary>
|
||||
/// Having the raid timer reach zero results in a successful extract,
|
||||
/// this patch makes it so letting the time reach zero results in a MIA result
|
||||
/// </summary>
|
||||
public class EndByTimerPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = PatchConstants.LocalGameType.BaseType;
|
||||
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags).SingleOrDefault(IsStopRaidMethod);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
private static bool IsStopRaidMethod(MethodInfo mi)
|
||||
{
|
||||
var parameters = mi.GetParameters();
|
||||
return (parameters.Length == 4
|
||||
&& parameters[0].Name == "profileId"
|
||||
&& parameters[1].Name == "exitStatus"
|
||||
&& parameters[2].Name == "exitName"
|
||||
&& parameters[3].Name == "delay"
|
||||
&& parameters[0].ParameterType == typeof(string)
|
||||
&& parameters[1].ParameterType == typeof(ExitStatus)
|
||||
&& parameters[2].ParameterType == typeof(string)
|
||||
&& parameters[3].ParameterType == typeof(float));
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PrefixPatch(object __instance, ref ExitStatus exitStatus, ref string exitName)
|
||||
{
|
||||
// No extract name and successful, its a MIA
|
||||
if (string.IsNullOrEmpty(exitName?.Trim()) && exitStatus == ExitStatus.Survived)
|
||||
{
|
||||
exitStatus = ExitStatus.MissingInAction;
|
||||
exitName = null;
|
||||
}
|
||||
|
||||
return true; // Do original
|
||||
}
|
||||
}
|
||||
}
|
49
project/Aki.SinglePlayer/Patches/Quests/SpawnPmcPatch.cs
Normal file
49
project/Aki.SinglePlayer/Patches/Quests/SpawnPmcPatch.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Quests
|
||||
{
|
||||
public class SpawnPmcPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = PatchConstants.EftTypes.Single(IsTargetType);
|
||||
var desiredMethod = desiredType.GetMethod("method_1", PatchConstants.PrivateFlags);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
private static bool IsTargetType(Type type)
|
||||
{
|
||||
if (!typeof(IBotData).IsAssignableFrom(type) || type.GetMethod("method_1", PatchConstants.PrivateFlags) == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fields = type.GetFields(PatchConstants.PrivateFlags);
|
||||
return fields.Any(f => f.FieldType != typeof(WildSpawnType)) && fields.Any(f => f.FieldType == typeof(BotDifficulty));
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref bool __result, object __instance, WildSpawnType ___wildSpawnType_0, BotDifficulty ___botDifficulty_0, Profile x)
|
||||
{
|
||||
if (x == null)
|
||||
{
|
||||
__result = false;
|
||||
Logger.LogInfo($"profile x was null, ___wildSpawnType_0 = {___wildSpawnType_0}");
|
||||
return false; // Skip original
|
||||
}
|
||||
|
||||
__result = x.Info.Settings.Role == ___wildSpawnType_0 && x.Info.Settings.BotDifficulty == ___botDifficulty_0;
|
||||
|
||||
return false; // Skip original
|
||||
}
|
||||
}
|
||||
}
|
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