mirror of
https://github.com/sp-tarkov/modules.git
synced 2025-02-12 16:50:43 -05:00
0.13.5.0 (!33)
Co-authored-by: Dev <dev@dev.sp-tarkov.com> Co-authored-by: CWX <CWX@noreply.dev.sp-tarkov.com> Co-authored-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com> Co-authored-by: RaiRaiTheRaichu <rairaitheraichu@noreply.dev.sp-tarkov.com> Co-authored-by: CWX <cwx@noreply.dev.sp-tarkov.com> Co-authored-by: Kaeno <e> Reviewed-on: SPT-AKI/Modules#33
This commit is contained in:
parent
5e881d263f
commit
1e238c426e
4
.gitignore
vendored
4
.gitignore
vendored
@ -38,6 +38,10 @@ bld/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
|
||||
# Rider directory
|
||||
.idea/
|
||||
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
|
@ -23,8 +23,8 @@ git config --local user.email "USERNAME@SOMETHING.com"
|
||||
|
||||
## Requirements
|
||||
|
||||
- Escape From Tarkov 25206
|
||||
- BepInEx 5.4.19
|
||||
- Escape From Tarkov 26282
|
||||
- BepInEx 5.4.21
|
||||
- Visual Studio Code
|
||||
- .NET 6 SDK
|
||||
|
||||
|
@ -24,6 +24,7 @@ namespace Aki.Core
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"A PATCH IN {GetType().Name} FAILED. SUBSEQUENT PATCHES HAVE NOT LOADED");
|
||||
Logger.LogError($"{GetType().Name}: {ex}");
|
||||
throw;
|
||||
}
|
||||
|
@ -15,7 +15,14 @@ namespace Aki.Core.Patches
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = PatchConstants.EftTypes.Single(t => t.Name == "Class227");
|
||||
_ = GClass239.DEBUG_LOGIC; // UPDATE BELOW LINE TOO
|
||||
var type = PatchConstants.EftTypes.Single(t => t.Name == "Class239");
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new Exception($"{nameof(TransportPrefixPatch)} failed: Could not find type to patch.");
|
||||
}
|
||||
|
||||
var value = Traverse.Create(type).Field("TransportPrefixes").GetValue<Dictionary<ETransportProtocolType, string>>();
|
||||
value[ETransportProtocolType.HTTPS] = "http://";
|
||||
value[ETransportProtocolType.WSS] = "ws://";
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Custom.Airdrops.Models;
|
||||
using Aki.Custom.Airdrops.Models;
|
||||
using Aki.Custom.Airdrops.Utils;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
@ -12,25 +11,33 @@ namespace Aki.Custom.Airdrops
|
||||
private AirdropPlane airdropPlane;
|
||||
private AirdropBox airdropBox;
|
||||
private ItemFactoryUtil factory;
|
||||
|
||||
public bool isFlareDrop;
|
||||
private AirdropParametersModel airdropParameters;
|
||||
|
||||
public async void Start()
|
||||
public async void Awake()
|
||||
{
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
|
||||
if (gameWorld == null)
|
||||
try
|
||||
{
|
||||
Destroy(this);
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
|
||||
if (gameWorld == null)
|
||||
{
|
||||
Destroy(this);
|
||||
}
|
||||
|
||||
airdropParameters = AirdropUtil.InitAirdropParams(gameWorld, isFlareDrop);
|
||||
|
||||
if (!airdropParameters.AirdropAvailable)
|
||||
{
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
airdropParameters = AirdropUtil.InitAirdropParams(gameWorld, isFlareDrop);
|
||||
|
||||
if (!airdropParameters.AirdropAvailable)
|
||||
catch
|
||||
{
|
||||
Debug.LogError("[AKI-AIRDROPS]: Unable to get config from server, airdrop won't occur");
|
||||
Destroy(this);
|
||||
return;
|
||||
throw;
|
||||
}
|
||||
|
||||
try
|
||||
@ -55,34 +62,47 @@ namespace Aki.Custom.Airdrops
|
||||
|
||||
public void FixedUpdate()
|
||||
{
|
||||
airdropParameters.Timer += 0.02f;
|
||||
if (airdropParameters == null || airdropPlane == null || airdropBox == null) return;
|
||||
|
||||
if (airdropParameters.Timer >= airdropParameters.TimeToStart && !airdropParameters.PlaneSpawned)
|
||||
try
|
||||
{
|
||||
StartPlane();
|
||||
}
|
||||
airdropParameters.Timer += 0.02f;
|
||||
|
||||
if (!airdropParameters.PlaneSpawned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (airdropParameters.Timer >= airdropParameters.TimeToStart && !airdropParameters.PlaneSpawned)
|
||||
{
|
||||
StartPlane();
|
||||
}
|
||||
|
||||
if (airdropParameters.DistanceTraveled >= airdropParameters.DistanceToDrop && !airdropParameters.BoxSpawned)
|
||||
{
|
||||
StartBox();
|
||||
BuildLootContainer(airdropParameters.Config);
|
||||
}
|
||||
if (!airdropParameters.PlaneSpawned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (airdropParameters.DistanceTraveled < airdropParameters.DistanceToTravel)
|
||||
{
|
||||
airdropParameters.DistanceTraveled += Time.deltaTime * airdropParameters.Config.PlaneSpeed;
|
||||
var distanceToDrop = airdropParameters.DistanceToDrop - airdropParameters.DistanceTraveled;
|
||||
airdropPlane.ManualUpdate(distanceToDrop);
|
||||
if (airdropParameters.DistanceTraveled >= airdropParameters.DistanceToDrop && !airdropParameters.BoxSpawned)
|
||||
{
|
||||
StartBox();
|
||||
BuildLootContainer(airdropParameters.Config);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch
|
||||
{
|
||||
Debug.LogError("[AKI-AIRDROPS]: An error occurred during the airdrop FixedUpdate process");
|
||||
Destroy(airdropBox.gameObject);
|
||||
Destroy(airdropPlane.gameObject);
|
||||
Destroy(this);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,12 +43,15 @@ namespace Aki.Custom.Airdrops.Utils
|
||||
if (item.IsPreset)
|
||||
{
|
||||
actualItem = itemFactory.GetPresetItem(item.Tpl);
|
||||
actualItem.SpawnedInSession = true;
|
||||
actualItem.GetAllItems().ExecuteForEach(x => x.SpawnedInSession = true);
|
||||
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;
|
||||
actualItem.SpawnedInSession = true;
|
||||
|
||||
resources = actualItem.Template.AllResources.ToArray();
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ namespace Aki.Custom
|
||||
new SessionIdPatch().Enable();
|
||||
new VersionLabelPatch().Enable();
|
||||
new IsEnemyPatch().Enable();
|
||||
new LocationLootCacheBustingPatch().Enable();
|
||||
//new AddSelfAsEnemyPatch().Enable();
|
||||
new CheckAndAddEnemyPatch().Enable();
|
||||
new BotSelfEnemyPatch().Enable(); // needed
|
||||
@ -42,9 +43,13 @@ namespace Aki.Custom
|
||||
new ExitWhileLootingPatch().Enable();
|
||||
new QTEPatch().Enable();
|
||||
new PmcFirstAidPatch().Enable();
|
||||
new SettingsLocationPatch().Enable();
|
||||
//new RankPanelPatch().Enable();
|
||||
new RagfairFeePatch().Enable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"A PATCH IN {GetType().Name} FAILED. SUBSEQUENT PATCHES HAVE NOT LOADED");
|
||||
Logger.LogError($"{GetType().Name}: {ex}");
|
||||
throw;
|
||||
}
|
||||
|
146
project/Aki.Custom/CustomAI/AIBrainSpawnWeightAdjustment.cs
Normal file
146
project/Aki.Custom/CustomAI/AIBrainSpawnWeightAdjustment.cs
Normal file
@ -0,0 +1,146 @@
|
||||
using Aki.Common.Http;
|
||||
using BepInEx.Logging;
|
||||
using EFT;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Aki.Custom.CustomAI
|
||||
{
|
||||
public class AIBrainSpawnWeightAdjustment
|
||||
{
|
||||
private static AIBrains aiBrainsCache = null;
|
||||
private static DateTime aiBrainCacheDate = new DateTime();
|
||||
private static readonly Random random = new Random();
|
||||
private static readonly List<WildSpawnType> playerScavTypes = new List<WildSpawnType>() { WildSpawnType.bossKilla, WildSpawnType.pmcBot, WildSpawnType.bossGluhar };
|
||||
private readonly ManualLogSource logger;
|
||||
|
||||
public AIBrainSpawnWeightAdjustment(ManualLogSource logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public WildSpawnType GetRandomisedPlayerScavType()
|
||||
{
|
||||
return playerScavTypes.Random();
|
||||
}
|
||||
|
||||
public WildSpawnType GetAssaultScavWildSpawnType(BotOwner botOwner, string currentMapName)
|
||||
{
|
||||
// Get map brain weights from server and cache
|
||||
if (aiBrainsCache == null || CacheIsStale())
|
||||
{
|
||||
ResetCacheDate();
|
||||
HydrateCacheWithServerData();
|
||||
|
||||
if (!aiBrainsCache.assault.TryGetValue(currentMapName.ToLower(), out _))
|
||||
{
|
||||
throw new Exception($"Bots were refreshed from the server but the assault cache still doesnt contain data");
|
||||
}
|
||||
}
|
||||
|
||||
// Choose random weighted brain
|
||||
var randomType = WeightedRandom(aiBrainsCache.assault[currentMapName.ToLower()].Keys.ToArray(), aiBrainsCache.assault[currentMapName.ToLower()].Values.ToArray());
|
||||
if (Enum.TryParse(randomType, out WildSpawnType newAiType))
|
||||
{
|
||||
logger.LogWarning($"Updated assault bot to use: {newAiType} brain");
|
||||
return newAiType;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning($"Updated assault bot {botOwner.Profile.Info.Nickname}: {botOwner.Profile.Info.Settings.Role} to use: {newAiType} brain");
|
||||
|
||||
return newAiType;
|
||||
}
|
||||
}
|
||||
|
||||
public WildSpawnType GetPmcWildSpawnType(BotOwner botOwner_0, WildSpawnType pmcType, string currentMapName)
|
||||
{
|
||||
if (aiBrainsCache == null || !aiBrainsCache.pmc.TryGetValue(pmcType, out var botSettings) || CacheIsStale())
|
||||
{
|
||||
ResetCacheDate();
|
||||
HydrateCacheWithServerData();
|
||||
|
||||
if (!aiBrainsCache.pmc.TryGetValue(pmcType, out botSettings))
|
||||
{
|
||||
throw new Exception($"Bots were refreshed from the server but the cache still doesnt contain an appropriate bot for type {botOwner_0.Profile.Info.Settings.Role}");
|
||||
}
|
||||
}
|
||||
|
||||
var mapSettings = botSettings[currentMapName.ToLower()];
|
||||
var randomType = WeightedRandom(mapSettings.Keys.ToArray(), mapSettings.Values.ToArray());
|
||||
if (Enum.TryParse(randomType, out WildSpawnType newAiType))
|
||||
{
|
||||
logger.LogWarning($"Updated spt bot {botOwner_0.Profile.Info.Nickname}: {botOwner_0.Profile.Info.Settings.Role} to use: {newAiType} brain");
|
||||
|
||||
return newAiType;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogError($"Couldnt not update spt bot {botOwner_0.Profile.Info.Nickname} to random type {randomType}, does not exist for WildSpawnType enum, defaulting to 'assault'");
|
||||
|
||||
return WildSpawnType.assault;
|
||||
}
|
||||
}
|
||||
|
||||
private void HydrateCacheWithServerData()
|
||||
{
|
||||
// Get weightings for PMCs from server and store in dict
|
||||
var result = RequestHandler.GetJson($"/singleplayer/settings/bot/getBotBehaviours/");
|
||||
aiBrainsCache = JsonConvert.DeserializeObject<AIBrains>(result);
|
||||
logger.LogWarning($"Cached ai brain weights in client");
|
||||
}
|
||||
|
||||
private void ResetCacheDate()
|
||||
{
|
||||
aiBrainCacheDate = DateTime.Now;
|
||||
aiBrainsCache?.pmc?.Clear();
|
||||
aiBrainsCache?.assault?.Clear();
|
||||
}
|
||||
|
||||
private static bool CacheIsStale()
|
||||
{
|
||||
TimeSpan cacheAge = DateTime.Now - aiBrainCacheDate;
|
||||
|
||||
return cacheAge.Minutes > 15;
|
||||
}
|
||||
|
||||
public class AIBrains
|
||||
{
|
||||
public Dictionary<WildSpawnType, Dictionary<string, Dictionary<string, int>>> pmc { get; set; }
|
||||
public Dictionary<string, Dictionary<string, int>> assault { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choose a value from a choice of values with weightings
|
||||
/// </summary>
|
||||
/// <param name="botTypes"></param>
|
||||
/// <param name="weights"></param>
|
||||
/// <returns></returns>
|
||||
private string WeightedRandom(string[] botTypes, int[] weights)
|
||||
{
|
||||
var cumulativeWeights = new int[botTypes.Length];
|
||||
|
||||
for (int i = 0; i < weights.Length; i++)
|
||||
{
|
||||
cumulativeWeights[i] = weights[i] + (i == 0 ? 0 : cumulativeWeights[i - 1]);
|
||||
}
|
||||
|
||||
var maxCumulativeWeight = cumulativeWeights[cumulativeWeights.Length - 1];
|
||||
var randomNumber = maxCumulativeWeight * random.NextDouble();
|
||||
|
||||
for (var itemIndex = 0; itemIndex < botTypes.Length; itemIndex++)
|
||||
{
|
||||
if (cumulativeWeights[itemIndex] >= randomNumber)
|
||||
{
|
||||
return botTypes[itemIndex];
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogError("failed to get random bot weighting, returned assault");
|
||||
|
||||
return "assault";
|
||||
}
|
||||
}
|
||||
}
|
47
project/Aki.Custom/CustomAI/AiHelpers.cs
Normal file
47
project/Aki.Custom/CustomAI/AiHelpers.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using Aki.PrePatch;
|
||||
using EFT;
|
||||
|
||||
namespace Aki.Custom.CustomAI
|
||||
{
|
||||
public static class AiHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Bot is a PMC when it has IsStreamerModeAvailable flagged and has a wildspawn type of 'sptBear' or 'sptUsec'
|
||||
/// </summary>
|
||||
/// <param name="botRoleToCheck">Bots role</param>
|
||||
/// <param name="___botOwner_0">Bot details</param>
|
||||
/// <returns></returns>
|
||||
public static bool BotIsSptPmc(WildSpawnType botRoleToCheck, BotOwner ___botOwner_0)
|
||||
{
|
||||
if (___botOwner_0.Profile.Info.IsStreamerModeAvailable)
|
||||
{
|
||||
// PMCs can sometimes have thier role changed to 'assaultGroup' by the client, we need a alternate way to figure out if they're a spt pmc
|
||||
return true;
|
||||
}
|
||||
|
||||
return (int)botRoleToCheck == AkiBotsPrePatcher.sptBearValue || (int)botRoleToCheck == AkiBotsPrePatcher.sptUsecValue;
|
||||
}
|
||||
|
||||
public static bool BotIsPlayerScav(WildSpawnType role, BotOwner ___botOwner_0)
|
||||
{
|
||||
if (___botOwner_0.Profile.Info.Nickname.Contains("(") && role == WildSpawnType.assault)
|
||||
{
|
||||
// Check bot is pscav by looking for the opening parentheses of their nickname e.g. scavname (pmc name)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool BotIsNormalAssaultScav(WildSpawnType role, BotOwner ___botOwner_0)
|
||||
{
|
||||
// Is assault + no (
|
||||
if (!___botOwner_0.Profile.Info.Nickname.Contains("(") && role == WildSpawnType.assault)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
122
project/Aki.Custom/CustomAI/PmcFoundInRaidEquipment.cs
Normal file
122
project/Aki.Custom/CustomAI/PmcFoundInRaidEquipment.cs
Normal file
@ -0,0 +1,122 @@
|
||||
using EFT.InventoryLogic;
|
||||
using EFT;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BepInEx.Logging;
|
||||
|
||||
namespace Aki.Custom.CustomAI
|
||||
{
|
||||
public class PmcFoundInRaidEquipment
|
||||
{
|
||||
private static readonly string magazineId = "5448bc234bdc2d3c308b4569";
|
||||
private static readonly string drugId = "5448f3a14bdc2d27728b4569";
|
||||
private static readonly string mediKitItem = "5448f39d4bdc2d0a728b4568";
|
||||
private static readonly string medicalItemId = "5448f3ac4bdc2dce718b4569";
|
||||
private static readonly string injectorItemId = "5448f3a64bdc2d60728b456a";
|
||||
private static readonly string throwableItemId = "543be6564bdc2df4348b4568";
|
||||
private static readonly string ammoItemId = "5485a8684bdc2da71d8b4567";
|
||||
private static readonly string weaponId = "5422acb9af1c889c16000029";
|
||||
private static readonly List<string> nonFiRItems = new List<string>() { magazineId, drugId, mediKitItem, medicalItemId, injectorItemId, throwableItemId, ammoItemId };
|
||||
|
||||
private static readonly string pistolId = "5447b5cf4bdc2d65278b4567";
|
||||
private static readonly string smgId = "5447b5e04bdc2d62278b4567";
|
||||
private static readonly string assaultRifleId = "5447b5f14bdc2d61278b4567";
|
||||
private static readonly string assaultCarbineId = "5447b5fc4bdc2d87278b4567";
|
||||
private static readonly string shotgunId = "5447b6094bdc2dc3278b4567";
|
||||
private static readonly string marksmanRifleId = "5447b6194bdc2d67278b4567";
|
||||
private static readonly string sniperRifleId = "5447b6254bdc2dc3278b4568";
|
||||
private static readonly string machinegunId = "5447bed64bdc2d97278b4568";
|
||||
private static readonly string grenadeLauncherId = "5447bedf4bdc2d87278b4568";
|
||||
private static readonly string knifeId = "5447e1d04bdc2dff2f8b4567";
|
||||
|
||||
private static readonly List<string> weaponTypeIds = new List<string>() { pistolId, smgId, assaultRifleId, assaultCarbineId, shotgunId, marksmanRifleId, sniperRifleId, machinegunId, grenadeLauncherId, knifeId };
|
||||
private readonly ManualLogSource logger;
|
||||
|
||||
public PmcFoundInRaidEquipment(ManualLogSource logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public void ConfigurePMCFindInRaidStatus(BotOwner ___botOwner_0)
|
||||
{
|
||||
// Must run before the container loot code, otherwise backpack loot is not FiR
|
||||
MakeEquipmentNotFiR(___botOwner_0);
|
||||
|
||||
// Get inventory items that hold other items (backpack/rig/pockets)
|
||||
List<Slot> containerGear = ___botOwner_0.Profile.Inventory.Equipment.GetContainerSlots();
|
||||
foreach (var container in containerGear)
|
||||
{
|
||||
foreach (var item in container.ContainedItem.GetAllItems())
|
||||
{
|
||||
// Skip items that match container (array has itself as an item)
|
||||
if (item.Id == container.Items.FirstOrDefault().Id)
|
||||
{
|
||||
//this.logger.LogError($"Skipping item {item.Id} {item.Name} as its same as container {container.FullId}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Dont add FiR to tacvest items PMC usually brings into raid (meds/mags etc)
|
||||
if (container.Name == "TacticalVest" && nonFiRItems.Any(item.Template._parent.Contains))
|
||||
{
|
||||
//this.logger.LogError($"Skipping item {item.Id} {item.Name} as its on the item type blacklist");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't add FiR to weapons in backpack (server sometimes adds pre-made weapons to backpack to simulate PMCs looting bodies)
|
||||
if (container.Name == "Backpack" && weaponTypeIds.Any(item.Template._parent.Contains))
|
||||
{
|
||||
//this.logger.LogError($"Skipping item {item.Id} {item.Name} as its on the item type blacklist");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't add FiR to grenades/mags/ammo in pockets
|
||||
if (container.Name == "Pockets" && new List<string> { throwableItemId, ammoItemId, magazineId, medicalItemId }.Any(item.Template._parent.Contains))
|
||||
{
|
||||
//this.logger.LogError($"Skipping item {item.Id} {item.Name} as its on the item type blacklist");
|
||||
continue;
|
||||
}
|
||||
|
||||
//Logger.LogError($"flagging item FiR: {item.Id} {item.Name} _parent: {item.Template._parent}");
|
||||
item.SpawnedInSession = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set dogtag as FiR
|
||||
var dogtag = ___botOwner_0.Profile.Inventory.GetItemsInSlots(new EquipmentSlot[] { EquipmentSlot.Dogtag });
|
||||
dogtag.FirstOrDefault().SpawnedInSession = true;
|
||||
}
|
||||
|
||||
|
||||
private void MakeEquipmentNotFiR(BotOwner ___botOwner_0)
|
||||
{
|
||||
var additionalItems = ___botOwner_0.Profile.Inventory.GetItemsInSlots(new EquipmentSlot[]
|
||||
{ EquipmentSlot.Backpack,
|
||||
EquipmentSlot.FirstPrimaryWeapon,
|
||||
EquipmentSlot.SecondPrimaryWeapon,
|
||||
EquipmentSlot.TacticalVest,
|
||||
EquipmentSlot.ArmorVest,
|
||||
EquipmentSlot.Scabbard,
|
||||
EquipmentSlot.Eyewear,
|
||||
EquipmentSlot.Headwear,
|
||||
EquipmentSlot.Earpiece,
|
||||
EquipmentSlot.ArmBand,
|
||||
EquipmentSlot.FaceCover,
|
||||
EquipmentSlot.Holster,
|
||||
EquipmentSlot.SecuredContainer
|
||||
});
|
||||
|
||||
foreach (var item in additionalItems)
|
||||
{
|
||||
// Some items are null, probably because bot doesnt have that particular slot on them
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//Logger.LogError($"flagging item FiR: {item.Id} {item.Name} _parent: {item.Template._parent}");
|
||||
item.SpawnedInSession = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -16,11 +16,11 @@ namespace Aki.Custom.Patches
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(BotGroupClass).GetMethod(methodName);
|
||||
return typeof(BotZoneGroupsDictionary).GetMethod(methodName);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(BotGroupClass __instance, IAIDetails person)
|
||||
private static bool PatchPrefix(BotZoneGroupsDictionary __instance, IPlayer person)
|
||||
{
|
||||
var botOwners = (List<BotOwner>)__instance.GetType().GetField("list_1", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance);
|
||||
if (botOwners.Any(x => x.Id == person.Id))
|
||||
|
@ -19,7 +19,7 @@ namespace Aki.Custom.Patches
|
||||
|
||||
private bool IsTargetType(Type type)
|
||||
{
|
||||
if (type.Name == nameof(BotControllerClass) && type.GetMethod(methodName) != null)
|
||||
if (type.Name == nameof(BotsController) && type.GetMethod(methodName) != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -42,7 +42,7 @@ namespace Aki.Custom.Patches
|
||||
/// This should fix that.
|
||||
/// </summary>
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(BotControllerClass __instance, IAIDetails aggressor, IAIDetails groupOwner, IAIDetails target)
|
||||
private static bool PatchPrefix(BotsController __instance, IPlayer aggressor, IPlayer groupOwner, IPlayer target)
|
||||
{
|
||||
BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone;
|
||||
foreach (var item in __instance.Groups())
|
||||
@ -63,7 +63,7 @@ namespace Aki.Custom.Patches
|
||||
&& group.ShallRevengeFor(target)
|
||||
)
|
||||
{
|
||||
group.AddEnemy(aggressor);
|
||||
group.AddEnemy(aggressor, EBotEnemyCause.AddEnemyToAllGroupsInBotZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Aki.Common.Http;
|
||||
using EFT;
|
||||
using EFT.UI;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
@ -22,7 +23,13 @@ namespace Aki.Custom.Patches
|
||||
private static bool PatchPrefix(ref string __result, BotDifficulty botDifficulty, WildSpawnType role)
|
||||
{
|
||||
__result = RequestHandler.GetJson($"/singleplayer/settings/bot/difficulty/{role}/{botDifficulty}");
|
||||
return string.IsNullOrWhiteSpace(__result);
|
||||
var resultIsNullEmpty = string.IsNullOrWhiteSpace(__result);
|
||||
if (resultIsNullEmpty)
|
||||
{
|
||||
ConsoleScreen.LogError($"Unable to get difficulty settings for {role} {botDifficulty}");
|
||||
}
|
||||
|
||||
return resultIsNullEmpty; // Server data returned = false = skip original method
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace Aki.Custom.Patches
|
||||
|
||||
private bool IsTargetType(Type type)
|
||||
{
|
||||
if (type.Name == nameof(BotControllerClass) && type.GetMethod(methodName) != null)
|
||||
if (type.Name == nameof(BotsController) && type.GetMethod(methodName) != null)
|
||||
{
|
||||
Logger.LogInfo($"{methodName}: {type.FullName}");
|
||||
return true;
|
||||
@ -40,7 +40,7 @@ namespace Aki.Custom.Patches
|
||||
/// This should fix that.
|
||||
/// </summary>
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(BotControllerClass __instance, IAIDetails aggressor, IAIDetails groupOwner, IAIDetails target)
|
||||
private static bool PatchPrefix(BotsController __instance, IPlayer aggressor, IPlayer groupOwner, IPlayer target)
|
||||
{
|
||||
BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone;
|
||||
foreach (var item in __instance.Groups())
|
||||
@ -54,14 +54,14 @@ namespace Aki.Custom.Patches
|
||||
{
|
||||
if (!group.Enemies.ContainsKey(aggressor) && ShouldAttack(aggressor, target, group))
|
||||
{
|
||||
group.AddEnemy(aggressor);
|
||||
group.AddEnemy(aggressor, EBotEnemyCause.AddEnemyToAllGroupsInBotZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
private static bool ShouldAttack(IAIDetails attacker, IAIDetails victim, BotGroupClass groupToCheck)
|
||||
private static bool ShouldAttack(IPlayer attacker, IPlayer victim, BotsGroup 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
|
||||
|
@ -17,9 +17,9 @@ namespace Aki.Custom.Patches
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(BotOwner __instance, BotGroupClass group)
|
||||
private static bool PatchPrefix(BotOwner __instance, BotsGroup group)
|
||||
{
|
||||
IAIDetails selfToRemove = null;
|
||||
IPlayer selfToRemove = null;
|
||||
|
||||
foreach (var enemy in group.Enemies)
|
||||
{
|
||||
|
@ -41,7 +41,7 @@ namespace Aki.Custom.Patches
|
||||
/// removes the !player.AIData.IsAI check
|
||||
/// </summary>
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(BotGroupClass __instance, IAIDetails player, ref bool ignoreAI)
|
||||
private static bool PatchPrefix(BotsGroup __instance, IPlayer player, ref bool ignoreAI)
|
||||
{
|
||||
// Z already has player as enemy BUT Enemies dict is empty, adding them again causes 'existing key' errors
|
||||
if (__instance.InitialBotType == WildSpawnType.bossZryachiy || __instance.InitialBotType == WildSpawnType.followerZryachiy)
|
||||
@ -56,7 +56,7 @@ namespace Aki.Custom.Patches
|
||||
|
||||
if (!__instance.Enemies.ContainsKey(player))
|
||||
{
|
||||
__instance.AddEnemy(player);
|
||||
__instance.AddEnemy(player, EBotEnemyCause.checkAddTODO);
|
||||
}
|
||||
|
||||
return false; // Skip original
|
||||
|
@ -1,79 +1,91 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Patching;
|
||||
using EFT;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using Comfort.Common;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Aki.PrePatch;
|
||||
using Random = System.Random;
|
||||
using Aki.Custom.CustomAI;
|
||||
|
||||
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();
|
||||
private static readonly PmcFoundInRaidEquipment pmcFoundInRaidEquipment = new PmcFoundInRaidEquipment(Logger);
|
||||
private static readonly AIBrainSpawnWeightAdjustment aIBrainSpawnWeightAdjustment = new AIBrainSpawnWeightAdjustment(Logger);
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(BotBrainClass).GetMethod("Activate", BindingFlags.Public | BindingFlags.Instance);
|
||||
return typeof(StandartBotBrain).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
|
||||
/// Postfix will adjust it back to original type
|
||||
/// </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)
|
||||
private static bool PatchPrefix(out WildSpawnType __state, StandartBotBrain __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}");
|
||||
___botOwner_0.Profile.Info.Settings.Role = FixAssaultGroupPmcsRole(___botOwner_0);
|
||||
__state = ___botOwner_0.Profile.Info.Settings.Role; // Store original type in state param to allow access in PatchPostFix()
|
||||
try
|
||||
{
|
||||
if (BotIsSptPmc(___botOwner_0.Profile.Info.Settings.Role))
|
||||
if (AiHelpers.BotIsPlayerScav(__state, ___botOwner_0))
|
||||
{
|
||||
string currentMapName = GetCurrentMap();
|
||||
___botOwner_0.Profile.Info.Settings.Role = aIBrainSpawnWeightAdjustment.GetRandomisedPlayerScavType();
|
||||
|
||||
if (!botTypeCache.TryGetValue(___botOwner_0.Profile.Info.Settings.Role, out var botSettings) || CacheIsStale())
|
||||
return true; // Do original
|
||||
}
|
||||
string currentMapName = GetCurrentMap();
|
||||
if (AiHelpers.BotIsNormalAssaultScav(__state, ___botOwner_0))
|
||||
{
|
||||
___botOwner_0.Profile.Info.Settings.Role = aIBrainSpawnWeightAdjustment.GetAssaultScavWildSpawnType(___botOwner_0, currentMapName);
|
||||
|
||||
return true; // Do original
|
||||
}
|
||||
|
||||
if (AiHelpers.BotIsSptPmc(__state, ___botOwner_0))
|
||||
{
|
||||
// Bot has inventory equipment
|
||||
if (___botOwner_0.Profile?.Inventory?.Equipment != null)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
pmcFoundInRaidEquipment.ConfigurePMCFindInRaidStatus(___botOwner_0);
|
||||
}
|
||||
|
||||
var mapSettings = botSettings[currentMapName.ToLower()];
|
||||
var randomType = WeightedRandom(mapSettings.Keys.ToArray(), mapSettings.Values.ToArray());
|
||||
if (Enum.TryParse(randomType, out WildSpawnType newAiType))
|
||||
{
|
||||
Logger.LogWarning($"Updated spt bot {___botOwner_0.Profile.Info.Nickname}: {___botOwner_0.Profile.Info.Settings.Role} to use: {newAiType} brain");
|
||||
___botOwner_0.Profile.Info.Settings.Role = newAiType;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError($"Couldnt not update spt bot {___botOwner_0.Profile.Info.Nickname} to random type {randomType}, does not exist for WildSpawnType enum");
|
||||
}
|
||||
___botOwner_0.Profile.Info.Settings.Role = aIBrainSpawnWeightAdjustment.GetPmcWildSpawnType(___botOwner_0, ___botOwner_0.Profile.Info.Settings.Role, currentMapName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error processing log: {ex.Message}");
|
||||
Logger.LogError($"Error running CustomAiPatch PatchPrefix(): {ex.Message}");
|
||||
Logger.LogError(ex.StackTrace);
|
||||
}
|
||||
|
||||
return true; // Do original
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the client sometimes replaces PMC roles with 'assaultGroup', give PMCs their original role back (sptBear/sptUsec)
|
||||
/// </summary>
|
||||
/// <returns>WildSpawnType</returns>
|
||||
private static WildSpawnType FixAssaultGroupPmcsRole(BotOwner botOwner)
|
||||
{
|
||||
if (botOwner.Profile.Info.IsStreamerModeAvailable && botOwner.Profile.Info.Settings.Role == WildSpawnType.assaultGroup)
|
||||
{
|
||||
Logger.LogError($"Broken PMC found: {botOwner.Profile.Nickname}, was {botOwner.Profile.Info.Settings.Role}");
|
||||
|
||||
// Its a PMC, figure out what the bot originally was and return it
|
||||
return botOwner.Profile.Info.Side == EPlayerSide.Bear
|
||||
? (WildSpawnType)AkiBotsPrePatcher.sptBearValue
|
||||
: (WildSpawnType)AkiBotsPrePatcher.sptUsecValue;
|
||||
}
|
||||
|
||||
// Not broken pmc, return original role
|
||||
return botOwner.Profile.Info.Settings.Role;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revert prefix change, get bots type back to what it was before changes
|
||||
/// </summary>
|
||||
@ -82,16 +94,16 @@ namespace Aki.Custom.Patches
|
||||
[PatchPostfix]
|
||||
private static void PatchPostFix(WildSpawnType __state, BotOwner ___botOwner_0)
|
||||
{
|
||||
if (BotIsSptPmc(__state))
|
||||
if (AiHelpers.BotIsSptPmc(__state, ___botOwner_0))
|
||||
{
|
||||
// Set spt bot bot back to original type
|
||||
___botOwner_0.Profile.Info.Settings.Role = __state;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool BotIsSptPmc(WildSpawnType role)
|
||||
{
|
||||
return (int)role == AkiBotsPrePatcher.sptBearValue || (int)role == AkiBotsPrePatcher.sptUsecValue;
|
||||
else if (AiHelpers.BotIsPlayerScav(__state, ___botOwner_0))
|
||||
{
|
||||
// Set pscav back to original type
|
||||
___botOwner_0.Profile.Info.Settings.Role = __state;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetCurrentMap()
|
||||
@ -100,50 +112,5 @@ namespace Aki.Custom.Patches
|
||||
|
||||
return gameWorld.MainPlayer.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);
|
||||
Logger.LogWarning($"Cached bot.json/pmcType PMC brain weights in client");
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogError("failed to get random bot weighting, returned assault");
|
||||
|
||||
return "assault";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ namespace Aki.Custom.Patches
|
||||
|
||||
_manifestField = type.GetField(nameof(EasyAssets.Manifest));
|
||||
_bundlesField = type.GetField($"{EasyBundleHelper.Type.Name.ToLowerInvariant()}_0", PatchConstants.PrivateFlags);
|
||||
|
||||
// DependencyGraph<IEasyBundle>
|
||||
_systemProperty = type.GetProperty("System");
|
||||
}
|
||||
|
||||
@ -82,7 +84,15 @@ namespace Aki.Custom.Patches
|
||||
|
||||
for (var i = 0; i < bundleNames.Length; i++)
|
||||
{
|
||||
bundles[i] = (IEasyBundle)Activator.CreateInstance(EasyBundleHelper.Type, new object[] { bundleNames[i], path, manifest, bundleLock, bundleCheck });
|
||||
bundles[i] = (IEasyBundle)Activator.CreateInstance(EasyBundleHelper.Type, new object[]
|
||||
{
|
||||
bundleNames[i],
|
||||
path,
|
||||
manifest,
|
||||
bundleLock,
|
||||
bundleCheck
|
||||
});
|
||||
|
||||
await JobScheduler.Yield(EJobPriority.Immediate);
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ namespace Aki.Custom.Patches
|
||||
var player = Singleton<GameWorld>.Instance.MainPlayer;
|
||||
if (profileId == player?.Profile.Id)
|
||||
{
|
||||
GClass2977.Instance.CloseAllScreensForced();
|
||||
GClass2897.Instance.CloseAllScreensForced();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -39,22 +39,22 @@ namespace Aki.Custom.Patches
|
||||
/// 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)
|
||||
private static bool PatchPrefix(ref bool __result, BotsGroup __instance, IPlayer requester)
|
||||
{
|
||||
var isEnemy = false; // default not an enemy
|
||||
if (requester == null)
|
||||
{
|
||||
__result = isEnemy;
|
||||
|
||||
return true; // Skip original
|
||||
return false; // Skip original
|
||||
}
|
||||
|
||||
// Check existing enemies list
|
||||
// Could also check x.Value.Player?.Id - BSG do it this way
|
||||
if (!__instance.Enemies.IsNullOrEmpty() && __instance.Enemies.Any(x=> x.Key.Id == requester.Id))
|
||||
if (!__instance.Enemies.IsNullOrEmpty() && __instance.Enemies.Any(x => x.Key.Id == requester.Id))
|
||||
{
|
||||
__result = true;
|
||||
return true;
|
||||
return false; // Skip original
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -62,7 +62,7 @@ namespace Aki.Custom.Patches
|
||||
// Make zryachiy use existing isEnemy() code
|
||||
if (__instance.InitialBotType == WildSpawnType.bossZryachiy)
|
||||
{
|
||||
return false; // do original method
|
||||
return false; // Skip original
|
||||
}
|
||||
|
||||
if (__instance.Side == EPlayerSide.Usec)
|
||||
@ -71,7 +71,7 @@ namespace Aki.Custom.Patches
|
||||
ShouldAttackUsec(requester))
|
||||
{
|
||||
isEnemy = true;
|
||||
__instance.AddEnemy(requester);
|
||||
__instance.AddEnemy(requester, EBotEnemyCause.checkAddTODO);
|
||||
}
|
||||
}
|
||||
else if (__instance.Side == EPlayerSide.Bear)
|
||||
@ -80,23 +80,28 @@ namespace Aki.Custom.Patches
|
||||
ShouldAttackBear(requester))
|
||||
{
|
||||
isEnemy = true;
|
||||
__instance.AddEnemy(requester);
|
||||
__instance.AddEnemy(requester, EBotEnemyCause.checkAddTODO);
|
||||
}
|
||||
}
|
||||
else if (__instance.Side == EPlayerSide.Savage)
|
||||
{
|
||||
if (requester.Side != EPlayerSide.Savage)
|
||||
{
|
||||
//Lets exUsec warn Usecs and fire at will at Bears
|
||||
if (__instance.InitialBotType == WildSpawnType.exUsec)
|
||||
{
|
||||
return true; // Let BSG handle things
|
||||
}
|
||||
// everyone else is an enemy to savage (scavs)
|
||||
isEnemy = true;
|
||||
__instance.AddEnemy(requester);
|
||||
__instance.AddEnemy(requester, EBotEnemyCause.checkAddTODO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__result = isEnemy;
|
||||
|
||||
return true; // Skip original
|
||||
return false; // Skip original
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -104,7 +109,7 @@ namespace Aki.Custom.Patches
|
||||
/// </summary>
|
||||
/// <param name="requester"></param>
|
||||
/// <returns>bool</returns>
|
||||
private static bool ShouldAttackUsec(IAIDetails requester)
|
||||
private static bool ShouldAttackUsec(IPlayer requester)
|
||||
{
|
||||
var requesterMind = requester?.AIData?.BotOwner?.Settings?.FileSettings?.Mind;
|
||||
|
||||
@ -121,7 +126,7 @@ namespace Aki.Custom.Patches
|
||||
/// </summary>
|
||||
/// <param name="requester"></param>
|
||||
/// <returns></returns>
|
||||
private static bool ShouldAttackBear(IAIDetails requester)
|
||||
private static bool ShouldAttackBear(IPlayer requester)
|
||||
{
|
||||
var requesterMind = requester.AIData?.BotOwner?.Settings?.FileSettings?.Mind;
|
||||
|
||||
|
40
project/Aki.Custom/Patches/LocationLootCacheBustingPatch.cs
Normal file
40
project/Aki.Custom/Patches/LocationLootCacheBustingPatch.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
/// <summary>
|
||||
/// BaseLocalGame appears to cache a maps loot data and reuse it when the variantId from method_6 is the same, this patch exits the method early, never caching the data
|
||||
/// </summary>
|
||||
public class LocationLootCacheBustingPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = PatchConstants.EftTypes.Single(x => x.Name == "LocalGame").BaseType; // BaseLocalGame
|
||||
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags).Single(x => IsTargetMethod(x)); // method_6
|
||||
|
||||
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 == "backendUrl"
|
||||
&& parameters[1].Name == "locationId"
|
||||
&& parameters[2].Name == "variantId";
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix()
|
||||
{
|
||||
return false; // skip original
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
@ -10,11 +13,30 @@ namespace Aki.Custom.Patches
|
||||
/// </summary>
|
||||
public class PmcFirstAidPatch : ModulePatch
|
||||
{
|
||||
private static Type _targetType;
|
||||
private static readonly string methodName = "FirstAidApplied";
|
||||
|
||||
public PmcFirstAidPatch()
|
||||
{
|
||||
_targetType = PatchConstants.EftTypes.FirstOrDefault(IsTargetType);
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(GClass399).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
return _targetType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GCLass350 for client version 25782
|
||||
/// </summary>
|
||||
private bool IsTargetType(Type type)
|
||||
{
|
||||
if (type.GetMethod("GetHpPercent") != null && type.GetMethod("TryApplyToCurrentPart") != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
|
49
project/Aki.Custom/Patches/RagfairFeePatch.cs
Normal file
49
project/Aki.Custom/Patches/RagfairFeePatch.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT.InventoryLogic;
|
||||
using EFT.UI.Ragfair;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
/// <summary>
|
||||
/// Send the tax amount for listing an item for sale on flea by player to server for later use when charging player
|
||||
/// Client doesnt send this data and calculating it server-side isn't accurate
|
||||
/// </summary>
|
||||
public class RagfairFeePatch : ModulePatch
|
||||
{
|
||||
public RagfairFeePatch()
|
||||
{
|
||||
// Remember to update prefix parameter if below lines are broken
|
||||
_ = nameof(GClass2859.IsAllSelectedItemSame);
|
||||
_ = nameof(GClass2859.AutoSelectSimilar);
|
||||
}
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(AddOfferWindow).GetMethod("method_1", PatchConstants.PrivateFlags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate tax to charge player and send to server before the offer is sent
|
||||
/// </summary>
|
||||
/// <param name="___item_0">Item sold</param>
|
||||
/// <param name="___gclass2859_0">OfferItemCount</param>
|
||||
/// <param name="___double_0">RequirementsPrice</param>
|
||||
/// <param name="___bool_0">SellInOnePiece</param>
|
||||
[PatchPrefix]
|
||||
private static void PatchPrefix(ref Item ___item_0, ref GClass2859 ___gclass2859_0, ref double ___double_0, ref bool ___bool_0)
|
||||
{
|
||||
RequestHandler.PutJson("/client/ragfair/offerfees", new
|
||||
{
|
||||
id = ___item_0.Id,
|
||||
tpl = ___item_0.TemplateId,
|
||||
count = ___gclass2859_0.OfferItemCount,
|
||||
fee = Mathf.CeilToInt((float)GClass1940.CalculateTaxPrice(___item_0, ___gclass2859_0.OfferItemCount, ___double_0, ___bool_0))
|
||||
}
|
||||
.ToJson());
|
||||
}
|
||||
}
|
||||
}
|
37
project/Aki.Custom/Patches/RankPanelPatch.cs
Normal file
37
project/Aki.Custom/Patches/RankPanelPatch.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using EFT.UI;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class RankPanelPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = typeof(RankPanel);
|
||||
var desiredMethod = desiredType.GetMethod("Show", PatchConstants.PublicFlags);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
||||
return desiredMethod;
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPreFix(ref int rankLevel, ref int maxRank)
|
||||
{
|
||||
if (Singleton<GameWorld>.Instance != null)
|
||||
{
|
||||
Logger.LogWarning("Rank Level: " + rankLevel.ToString() + " Max Rank Level: " + maxRank.ToString());
|
||||
ConsoleScreen.LogError("Rank Level: " + rankLevel.ToString() + " Max Rank Level: " + maxRank.ToString());
|
||||
ConsoleScreen.LogError("Game Broke!");
|
||||
Logger.LogWarning("This Shouldn't happen!! Please report this in discord");
|
||||
ConsoleScreen.LogError("This Shouldn't happen!! Please report this in discord");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
30
project/Aki.Custom/Patches/SettingsLocationPatch.cs
Normal file
30
project/Aki.Custom/Patches/SettingsLocationPatch.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
public class SettingsLocationPatch : ModulePatch
|
||||
{
|
||||
private static string _sptPath = Path.Combine(Environment.CurrentDirectory, "user", "sptSettings");
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return AccessTools.Constructor(typeof(SharedGameSettingsClass));
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
internal static void PatchPrefix(ref string ___string_0, ref string ___string_1)
|
||||
{
|
||||
if (!Directory.Exists(_sptPath))
|
||||
{
|
||||
Directory.CreateDirectory(_sptPath);
|
||||
}
|
||||
|
||||
___string_0 = _sptPath;
|
||||
___string_1 = _sptPath;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using EFT.UI;
|
||||
using HarmonyLib;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Comfort.Common;
|
||||
|
||||
namespace Aki.Custom.Patches
|
||||
{
|
||||
@ -40,8 +41,8 @@ namespace Aki.Custom.Patches
|
||||
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(Singleton<PreloaderUI>.Instance).Field("_alphaVersionLabel").Property("LocalizationKey").SetValue("{0}");
|
||||
Traverse.Create(Singleton<PreloaderUI>.Instance).Field("string_2").SetValue(_versionLabel);
|
||||
Traverse.Create(__result).Field("Major").SetValue(_versionLabel);
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,14 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Aki.Reflection.Utils;
|
||||
using UnityEngine;
|
||||
using BindableState = BindableState<Diz.DependencyManager.ELoadState>;
|
||||
|
||||
namespace Aki.Custom.Utils
|
||||
{
|
||||
public class EasyBundleHelper
|
||||
{
|
||||
private const BindingFlags _flags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
private const BindingFlags _NonPublicInstanceflags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
private static readonly FieldInfo _pathField;
|
||||
private static readonly FieldInfo _keyWithoutExtensionField;
|
||||
private static readonly FieldInfo _bundleLockField;
|
||||
@ -26,14 +27,24 @@ namespace Aki.Custom.Utils
|
||||
_ = 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));
|
||||
// Class can be found as a private array inside EasyAssets.cs, next to DependencyGraph<IEasyBundle>
|
||||
Type = PatchConstants.EftTypes.Single(x => x.GetMethod("set_SameNameAsset", _NonPublicInstanceflags) != null);
|
||||
|
||||
_pathField = Type.GetField("string_1", _NonPublicInstanceflags);
|
||||
_keyWithoutExtensionField = Type.GetField("string_0", _NonPublicInstanceflags);
|
||||
_bundleLockField = Type.GetFields(_NonPublicInstanceflags).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));
|
||||
|
||||
// Function with 0 params and returns task (usually method_0())
|
||||
var possibleMethods = Type.GetMethods(_NonPublicInstanceflags).Where(x => x.GetParameters().Length == 0 && x.ReturnType == typeof(Task));
|
||||
if (possibleMethods.Count() > 1)
|
||||
{
|
||||
Console.WriteLine($"Unable to find desired method as there are multiple possible matches: {string.Join(",", possibleMethods.Select(x => x.Name))}");
|
||||
}
|
||||
|
||||
_loadingCoroutineMethod = possibleMethods.SingleOrDefault();
|
||||
}
|
||||
|
||||
public EasyBundleHelper(object easyBundle)
|
||||
@ -113,9 +124,9 @@ namespace Aki.Custom.Utils
|
||||
}
|
||||
}
|
||||
|
||||
public Task LoadingCoroutine()
|
||||
public Task LoadingCoroutine(Dictionary<string, AssetBundle> bundles)
|
||||
{
|
||||
return (Task)_loadingCoroutineMethod.Invoke(_instance, new object[] { });
|
||||
return (Task)_loadingCoroutineMethod.Invoke(_instance, new object[] { bundles });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Assembly-CSharp" HintPath="..\Shared\Hollowed\hollowed.dll" Private="False" />
|
||||
<Reference Include="Sirenix.Serialization" HintPath="..\Shared\Managed\Sirenix.Serialization.dll" Private="False" />
|
||||
<Reference Include="Unity.TextMeshPro" HintPath="..\Shared\Managed\Unity.TextMeshPro.dll" Private="False" />
|
||||
<Reference Include="UnityEngine" HintPath="..\Shared\Managed\UnityEngine.dll" Private="False" />
|
||||
<Reference Include="UnityEngine.CoreModule" HintPath="..\Shared\Managed\UnityEngine.CoreModule.dll" Private="False" />
|
||||
|
@ -15,6 +15,7 @@ namespace Aki.Debugging
|
||||
try
|
||||
{
|
||||
// new CoordinatesPatch().Enable();
|
||||
new EndRaidDebug().Enable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
57
project/Aki.Debugging/Patches/EndRaidDebug.cs
Normal file
57
project/Aki.Debugging/Patches/EndRaidDebug.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using System.Reflection;
|
||||
using Aki.Reflection.Utils;
|
||||
using BepInEx.Logging;
|
||||
using EFT;
|
||||
using EFT.UI;
|
||||
using TMPro;
|
||||
|
||||
namespace Aki.Debugging.Patches
|
||||
{
|
||||
public class EndRaidDebug : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(TraderCard).GetMethod("method_0", PatchConstants.PrivateFlags);
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static bool PatchPreFix(ref LocalizedText ____nickName, ref TMP_Text ____standing,
|
||||
ref RankPanel ____rankPanel, ref Profile.GClass1625 ___gclass1625_0)
|
||||
{
|
||||
if (____nickName.LocalizationKey == null)
|
||||
{
|
||||
ConsoleScreen.LogError("This Shouldn't happen!! Please report this in discord");
|
||||
Logger.Log(LogLevel.Error, "[AKI] _nickName.LocalizationKey was null");
|
||||
}
|
||||
|
||||
if (____standing.text == null)
|
||||
{
|
||||
ConsoleScreen.LogError("This Shouldn't happen!! Please report this in discord");
|
||||
Logger.Log(LogLevel.Error, "[AKI] _standing.text was null");
|
||||
}
|
||||
|
||||
if (____rankPanel == null)
|
||||
{
|
||||
ConsoleScreen.LogError("This Shouldn't happen!! Please report this in discord");
|
||||
Logger.Log(LogLevel.Error, "[AKI] _rankPanel was null, skipping method_0");
|
||||
|
||||
return false; // skip original
|
||||
}
|
||||
|
||||
if (___gclass1625_0?.LoyaltyLevel == null)
|
||||
{
|
||||
ConsoleScreen.LogError("This Shouldn't happen!! Please report this in discord");
|
||||
Logger.Log(LogLevel.Error, "[AKI] _gclass1618_0 or _gclass1618_0.LoyaltyLevel was null");
|
||||
}
|
||||
|
||||
if (___gclass1625_0?.MaxLoyaltyLevel == null)
|
||||
{
|
||||
ConsoleScreen.LogError("This Shouldn't happen!! Please report this in discord");
|
||||
Logger.Log(LogLevel.Error, "[AKI] _gclass1618_0 or _gclass1618_0.MaxLoyaltyLevel was null");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,8 +7,8 @@ namespace Aki.PrePatch
|
||||
{
|
||||
public static IEnumerable<string> TargetDLLs { get; } = new[] { "Assembly-CSharp.dll" };
|
||||
|
||||
public static int sptUsecValue = 34;
|
||||
public static int sptBearValue = 35;
|
||||
public static int sptUsecValue = 38;
|
||||
public static int sptBearValue = 39;
|
||||
|
||||
public static void Patch(ref AssemblyDefinition assembly)
|
||||
{
|
||||
|
@ -53,9 +53,11 @@ namespace Aki.SinglePlayer
|
||||
new PluginErrorNotifierPatch().Enable();
|
||||
new SpawnProcessNegativeValuePatch().Enable();
|
||||
new InsuredItemManagerStartPatch().Enable();
|
||||
new MapReadyButtonPatch().Enable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"A PATCH IN {GetType().Name} FAILED. SUBSEQUENT PATCHES HAVE NOT LOADED");
|
||||
Logger.LogError($"{GetType().Name}: {ex}");
|
||||
throw;
|
||||
}
|
||||
|
@ -8,14 +8,13 @@ namespace Aki.SinglePlayer.Models.Progression
|
||||
{
|
||||
public class LighthouseProgressionClass : MonoBehaviour
|
||||
{
|
||||
private bool _isScav;
|
||||
private GameWorld _gameWorld;
|
||||
private Player _player;
|
||||
private float _timer;
|
||||
private bool _playerFlaggedAsEnemyToBosses;
|
||||
private List<MineDirectionalColliders> _bridgeMines;
|
||||
private RecodableItemClass _transmitter;
|
||||
private readonly List<IAIDetails> _zryachiyAndFollowers = new List<IAIDetails>();
|
||||
private readonly List<IPlayer> _zryachiyAndFollowers = new List<IPlayer>();
|
||||
private bool _aggressor;
|
||||
private bool _isDoorDisabled;
|
||||
private readonly string _transmitterId = "62e910aaf957f2915e0a5e36";
|
||||
@ -26,34 +25,37 @@ namespace Aki.SinglePlayer.Models.Progression
|
||||
_gameWorld = Singleton<GameWorld>.Instance;
|
||||
_player = _gameWorld?.MainPlayer;
|
||||
|
||||
// Exit if not on lighthouse
|
||||
if (_gameWorld == null || !string.Equals(_player.Location, "lighthouse", System.StringComparison.OrdinalIgnoreCase))
|
||||
if (_gameWorld == null || _player == null)
|
||||
{
|
||||
Destroy(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get transmitter from players inventory
|
||||
_transmitter = GetTransmitterFromInventory();
|
||||
|
||||
// Exit if transmitter does not exist and isnt green
|
||||
if (!PlayerHasActiveTransmitterInInventory())
|
||||
{
|
||||
Destroy(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject.Find("Attack").SetActive(false);
|
||||
|
||||
// Zone was added in a newer version and the gameObject actually has a \
|
||||
GameObject.Find("CloseZone\\").SetActive(false);
|
||||
|
||||
// Give access to Lightkeepers door
|
||||
_gameWorld.BufferZoneController.SetPlayerAccessStatus(_player.ProfileId, true);
|
||||
|
||||
// Expensive, run after gameworld / lighthouse checks above
|
||||
_bridgeMines = FindObjectsOfType<MineDirectionalColliders>().ToList();
|
||||
|
||||
// Player is a scav, exit
|
||||
if (_player.Side == EPlayerSide.Savage)
|
||||
{
|
||||
_isScav = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_transmitter = GetTransmitterFromInventory();
|
||||
if (PlayerHasTransmitterInInventory())
|
||||
{
|
||||
GameObject.Find("Attack").SetActive(false);
|
||||
|
||||
// Zone was added in a newer version and the gameObject actually has a \
|
||||
GameObject.Find("CloseZone\\").SetActive(false);
|
||||
|
||||
// Give access to Lightkeepers door
|
||||
_gameWorld.BufferZoneController.SetPlayerAccessStatus(_player.ProfileId, true);
|
||||
}
|
||||
// Set mines to be non-active
|
||||
SetBridgeMinesStatus(false);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@ -82,37 +84,28 @@ namespace Aki.SinglePlayer.Models.Progression
|
||||
SetupZryachiyAndFollowerHostility();
|
||||
}
|
||||
|
||||
if (_isScav)
|
||||
{
|
||||
MakeZryachiyAndFollowersHostileToPlayer();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// (active/green)
|
||||
if (PlayerHasActiveTransmitterInHands())
|
||||
{
|
||||
SetBridgeMinesStatus(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetBridgeMinesStatus(true);
|
||||
}
|
||||
|
||||
// If player becomes aggressor, block access to LK
|
||||
if (_aggressor)
|
||||
{
|
||||
DisableAccessToLightKeeper();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets transmitter from players inventory
|
||||
/// </summary>
|
||||
private RecodableItemClass GetTransmitterFromInventory()
|
||||
{
|
||||
return (RecodableItemClass)_player.Profile.Inventory.AllRealPlayerItems.FirstOrDefault(x => x.TemplateId == _transmitterId);
|
||||
return (RecodableItemClass) _player.Profile.Inventory.AllRealPlayerItems.FirstOrDefault(x => x.TemplateId == _transmitterId);
|
||||
}
|
||||
|
||||
private bool PlayerHasTransmitterInInventory()
|
||||
/// <summary>
|
||||
/// Checks for transmitter status and exists in players inventory
|
||||
/// </summary>
|
||||
private bool PlayerHasActiveTransmitterInInventory()
|
||||
{
|
||||
return _transmitter != null;
|
||||
return _transmitter != null &&
|
||||
_transmitter?.RecodableComponent?.Status == RadioTransmitterStatus.Green;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -123,12 +116,6 @@ namespace Aki.SinglePlayer.Models.Progression
|
||||
_timer += Time.deltaTime;
|
||||
}
|
||||
|
||||
private bool PlayerHasActiveTransmitterInHands()
|
||||
{
|
||||
return _gameWorld?.MainPlayer?.HandsController?.Item?.TemplateId == _transmitterId
|
||||
&& _transmitter?.RecodableComponent?.Status == RadioTransmitterStatus.Green;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set all brdige mines to desire state
|
||||
/// </summary>
|
||||
@ -142,11 +129,21 @@ namespace Aki.SinglePlayer.Models.Progression
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Put Zryachiy and followers into a list and sub to their death event
|
||||
/// Make player agressor if player kills them.
|
||||
/// </summary>
|
||||
private void SetupZryachiyAndFollowerHostility()
|
||||
{
|
||||
// only process non-players (ai)
|
||||
foreach (var aiBot in _gameWorld.AllAlivePlayersList.Where(x => !x.IsYourPlayer))
|
||||
{
|
||||
// Bots that die on mounted guns get stuck in AllAlivePlayersList, need to check health
|
||||
if (!aiBot.HealthController.IsAlive)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge case of bossZryachiy not being hostile to player
|
||||
if (aiBot.AIData.BotOwner.IsRole(WildSpawnType.bossZryachiy) || aiBot.AIData.BotOwner.IsRole(WildSpawnType.followerZryachiy))
|
||||
{
|
||||
@ -171,21 +168,6 @@ namespace Aki.SinglePlayer.Models.Progression
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterate over bots gathered from SetupZryachiyHostility()
|
||||
/// </summary>
|
||||
private void MakeZryachiyAndFollowersHostileToPlayer()
|
||||
{
|
||||
// If player is a scav, they must be added to the bosses enemy list otherwise they wont kill them
|
||||
foreach (var bot in _zryachiyAndFollowers)
|
||||
{
|
||||
bot.AIData.BotOwner.BotsGroup.CheckAndAddEnemy(_player);
|
||||
}
|
||||
|
||||
// Flag player was added to enemy list
|
||||
_playerFlaggedAsEnemyToBosses = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable door + set transmitter to 'red'
|
||||
/// </summary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT.HealthSystem;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.Healing
|
||||
|
@ -16,8 +16,23 @@ namespace Aki.SinglePlayer.Patches.MainMenu
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
//[CompilerGenerated]
|
||||
//private void method_67()
|
||||
//{
|
||||
// if (this.raidSettings_0.SelectedLocation.Id == "laboratory")
|
||||
// {
|
||||
// this.raidSettings_0.WavesSettings.IsBosses = true;
|
||||
// }
|
||||
// if (this.raidSettings_0.RaidMode == ERaidMode.Online)
|
||||
// {
|
||||
// this.method_40();
|
||||
// return;
|
||||
// }
|
||||
// this.method_41();
|
||||
//}
|
||||
|
||||
var desiredType = typeof(MainMenuController);
|
||||
var desiredMethod = desiredType.GetMethod("method_66", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var desiredMethod = desiredType.GetMethod("method_69", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
|
||||
|
@ -0,0 +1,26 @@
|
||||
using System.Reflection;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT.UI;
|
||||
using EFT.UI.Matchmaker;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.MainMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Removes the 'ready' button from the map preview screen - accessible by choosing map to deply to > clicking 'map' bottom left of screen
|
||||
/// Clicking the ready button makes a call to client/match/available, something we dont want
|
||||
/// </summary>
|
||||
public class MapReadyButtonPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(MatchmakerMapPointsScreen).GetMethod("Show", PatchConstants.PrivateFlags);
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostFix(ref DefaultUIButton ____readyButton)
|
||||
{
|
||||
____readyButton?.GameObject?.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -61,14 +61,13 @@ namespace Aki.SinglePlayer.Patches.MainMenu
|
||||
|
||||
// Show an error in the in-game console, we have to write this in reverse order because the
|
||||
// in-game console shows newer messages at the top
|
||||
ConsoleScreen consoleScreen = MonoBehaviourSingleton<PreloaderUI>.Instance.Console;
|
||||
foreach (string line in errorMessage.Split('\n').Reverse())
|
||||
{
|
||||
if (line.Length > 0)
|
||||
{
|
||||
// Note: We directly call the internal Log method to work around a bug in 'LogError' that passes an empty string
|
||||
// as the StackTrace parameter, which results in extra newlines being added to the console logs
|
||||
_directLogMethod.Invoke(consoleScreen, new object[] { line, null, LogType.Error });
|
||||
ConsoleScreen.LogError(line);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,12 +17,18 @@ namespace Aki.SinglePlayer.Patches.Progression
|
||||
private static void PatchPostfix()
|
||||
{
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
if (gameWorld == null || gameWorld.MainPlayer.Location.ToLower() != "lighthouse")
|
||||
|
||||
if (gameWorld == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameWorld.MainPlayer.Location.ToLower() != "lighthouse" || gameWorld.MainPlayer.Side == EPlayerSide.Savage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gameWorld.GetOrAddComponent<LighthouseProgressionClass>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using Comfort.Common;
|
||||
using EFT;
|
||||
using HarmonyLib;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@ -67,7 +68,7 @@ namespace Aki.SinglePlayer.Patches.Progression
|
||||
IsPlayerScav = ____raidSettings.IsScav
|
||||
};
|
||||
|
||||
RequestHandler.PutJson("/raid/profile/save", request.ToJson(_defaultJsonConverters.AddItem(new NotesJsonConverter()).ToArray()));
|
||||
RequestHandler.PutJson("/raid/profile/save", request.ToJson(_defaultJsonConverters.AddItem(new NotesJsonConverter()).ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ namespace Aki.SinglePlayer.Patches.Progression
|
||||
ESpawnCategory category,
|
||||
EPlayerSide side,
|
||||
string groupId,
|
||||
IAIDetails person,
|
||||
IPlayer person,
|
||||
string infiltration)
|
||||
{
|
||||
var spawnPointsField = (ISpawnPoints)__instance.GetType().GetFields(PatchConstants.PrivateFlags).SingleOrDefault(f => f.FieldType == typeof(ISpawnPoints))?.GetValue(__instance);
|
||||
@ -69,8 +69,9 @@ namespace Aki.SinglePlayer.Patches.Progression
|
||||
? 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}]");
|
||||
Logger.LogInfo($"Desired spawnpoint: [category:{category}] [side:{side}] [infil:{infiltration}] [{mapSpawnPoints.Count} total spawn points]");
|
||||
Logger.LogInfo($"Selected SpawnPoint: [id:{__result.Id}] [name:{__result.Name}] [category:{__result.Categories}] [side:{__result.Sides}] [infil:{__result.Infiltration}]");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -45,8 +45,8 @@ namespace Aki.SinglePlayer.Patches.Progression
|
||||
if (activeProfile.Side == EPlayerSide.Savage)
|
||||
{
|
||||
side = EPlayerSide.Savage; // Also set side to correct value (defaults to usec/bear when playing as scav)
|
||||
int xpGainedInSession = activeProfile.Stats.SessionCounters.GetAllInt(new object[] { CounterTag.Exp });
|
||||
activeProfile.Stats.TotalSessionExperience = (int)(xpGainedInSession * activeProfile.Stats.SessionExperienceMult * activeProfile.Stats.ExperienceBonusMult);
|
||||
int xpGainedInSession = activeProfile.Stats.Eft.SessionCounters.GetAllInt(new object[] { CounterTag.Exp });
|
||||
activeProfile.Stats.Eft.TotalSessionExperience = (int)(xpGainedInSession * activeProfile.Stats.Eft.SessionExperienceMult * activeProfile.Stats.Eft.ExperienceBonusMult);
|
||||
}
|
||||
|
||||
return true; // Always do original method
|
||||
|
@ -22,7 +22,7 @@ namespace Aki.SinglePlayer.Patches.Quests
|
||||
|
||||
private static bool IsTargetType(Type type)
|
||||
{
|
||||
if (!typeof(IBotData).IsAssignableFrom(type) || type.GetMethod("method_1", PatchConstants.PrivateFlags) == null)
|
||||
if (!typeof(IGetProfileData).IsAssignableFrom(type) || type.GetMethod("method_1", PatchConstants.PrivateFlags) == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
|
||||
static GetNewBotTemplatesPatch()
|
||||
{
|
||||
_ = nameof(IBotData.PrepareToLoadBackend);
|
||||
_ = nameof(IGetProfileData.PrepareToLoadBackend);
|
||||
_ = nameof(BotsPresets.GetNewProfile);
|
||||
_ = nameof(PoolManager.LoadBundlesAndCreatePools);
|
||||
_ = nameof(JobPriority.General);
|
||||
@ -58,7 +58,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
/// BotsPresets.GetNewProfile()
|
||||
/// </summary>
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref Task<Profile> __result, BotsPresets __instance, List<Profile> ___list_0, GClass628 data, ref bool withDelete)
|
||||
private static bool PatchPrefix(ref Task<Profile> __result, BotsPresets __instance, List<Profile> ___list_0, GClass513 data, ref bool withDelete)
|
||||
{
|
||||
/*
|
||||
When client wants new bot and GetNewProfile() return null (if not more available templates or they don't satisfy by Role and Difficulty condition)
|
||||
|
@ -2,6 +2,7 @@
|
||||
using EFT;
|
||||
using System.Reflection;
|
||||
using Aki.SinglePlayer.Utils.Insurance;
|
||||
using Comfort.Common;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
{
|
||||
@ -15,7 +16,26 @@ namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
[PatchPostfix]
|
||||
public static void PatchPostFix()
|
||||
{
|
||||
var gameWorld = Singleton<GameWorld>.Instance;
|
||||
|
||||
// Starts tracking of insured items manager
|
||||
InsuredItemManager.Instance.Init();
|
||||
|
||||
// Sets PlayerScavs items to FoundInRaid
|
||||
ConfigurePlayerScavFindInRaidStatus(gameWorld.MainPlayer);
|
||||
}
|
||||
|
||||
private static void ConfigurePlayerScavFindInRaidStatus(Player player)
|
||||
{
|
||||
if (player == null || player.Profile.Side != EPlayerSide.Savage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in player.Profile.Inventory.AllRealPlayerItems)
|
||||
{
|
||||
item.SpawnedInSession = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using TraderInfo = EFT.Profile.GClass1729;
|
||||
using TraderInfo = EFT.Profile.GClass1625;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
{
|
||||
|
@ -16,7 +16,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
|
||||
static RemoveUsedBotProfilePatch()
|
||||
{
|
||||
_ = nameof(IBotData.ChooseProfile);
|
||||
_ = nameof(IGetProfileData.ChooseProfile);
|
||||
|
||||
_flags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
_targetInterface = PatchConstants.EftTypes.Single(IsTargetInterface);
|
||||
@ -42,7 +42,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
/// <summary>
|
||||
/// BotsPresets.GetNewProfile()
|
||||
[PatchPrefix]
|
||||
private static bool PatchPrefix(ref Profile __result, object __instance, GClass628 data, ref bool withDelete)
|
||||
private static bool PatchPrefix(ref Profile __result, object __instance, GClass513 data, ref bool withDelete)
|
||||
{
|
||||
withDelete = true;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
using Aki.Common.Http;
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using System.Linq;
|
||||
using EFT;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
@ -19,7 +19,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
var desiredType = typeof(BotSpawnerClass);
|
||||
var desiredType = typeof(BotSpawner);
|
||||
var desiredMethod = desiredType.GetMethod("CheckOnMax", PatchConstants.PublicFlags);
|
||||
|
||||
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
|
||||
@ -29,13 +29,20 @@ namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static void PatchPreFix(ref int ___int_3)
|
||||
private static bool PatchPreFix(int wantSpawn, ref int toDelay, ref int toSpawn, ref int ____maxBots, int ____allBotsCount, int ____inSpawnProcess)
|
||||
{
|
||||
// Spawn process
|
||||
if (___int_3 < 0)
|
||||
|
||||
// Set bots to delay if alive bots + spawning bots count > maxbots
|
||||
// ____inSpawnProcess can be negative, don't go below 0 when calculating
|
||||
if ((____allBotsCount + Math.Max(____inSpawnProcess, 0)) > ____maxBots)
|
||||
{
|
||||
___int_3 = 0;
|
||||
toDelay += wantSpawn;
|
||||
toSpawn = 0;
|
||||
|
||||
return false; // Skip original
|
||||
}
|
||||
|
||||
return true; // Do original
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using Comfort.Common;
|
||||
using System.Reflection;
|
||||
using Aki.Reflection.Patching;
|
||||
using System.Collections;
|
||||
using EFT.HealthSystem;
|
||||
|
||||
namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
{
|
||||
@ -17,9 +18,9 @@ namespace Aki.SinglePlayer.Patches.RaidFix
|
||||
[PatchPrefix]
|
||||
static bool PatchPrefix()
|
||||
{
|
||||
bool shouldInvoke = typeof(ActiveHealthControllerClass)
|
||||
bool shouldInvoke = typeof(ActiveHealthController)
|
||||
.GetMethod("FindActiveEffect", BindingFlags.Instance | BindingFlags.Public)
|
||||
.MakeGenericMethod(typeof(ActiveHealthControllerClass)
|
||||
.MakeGenericMethod(typeof(ActiveHealthController)
|
||||
.GetNestedType("Stun", BindingFlags.Instance | BindingFlags.NonPublic))
|
||||
.Invoke(Singleton<GameWorld>.Instance.MainPlayer.ActiveHealthController, new object[] { EBodyPart.Common }) != null;
|
||||
|
||||
|
@ -29,10 +29,13 @@ namespace Aki.SinglePlayer.Patches.ScavMode
|
||||
_ = nameof(TimeAndWeatherSettings.IsRandomWeather);
|
||||
_ = nameof(BotControllerSettings.IsScavWars);
|
||||
_ = nameof(WavesSettings.IsBosses);
|
||||
_ = GClass2952.MAX_SCAV_COUNT; // UPDATE REFS TO THIS CLASS BELOW !!!
|
||||
|
||||
var menuControllerType = typeof(MainMenuController);
|
||||
|
||||
_onReadyScreenMethod = menuControllerType.GetMethod("method_39", PatchConstants.PrivateFlags);
|
||||
// `MatchmakerInsuranceScreen` OnShowNextScreen
|
||||
_onReadyScreenMethod = menuControllerType.GetMethod("method_42", PatchConstants.PrivateFlags);
|
||||
|
||||
_isLocalField = menuControllerType.GetField("bool_0", PatchConstants.PrivateFlags);
|
||||
_menuControllerField = typeof(TarkovApplication).GetFields(PatchConstants.PrivateFlags).FirstOrDefault(x => x.FieldType == typeof(MainMenuController));
|
||||
|
||||
@ -44,7 +47,8 @@ namespace Aki.SinglePlayer.Patches.ScavMode
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
return typeof(MainMenuController).GetMethod("method_63", PatchConstants.PrivateFlags);
|
||||
// `MatchMakerSelectionLocationScreen` OnShowNextScreen
|
||||
return typeof(MainMenuController).GetMethod("method_66", PatchConstants.PrivateFlags);
|
||||
}
|
||||
|
||||
[PatchTranspiler]
|
||||
@ -93,7 +97,7 @@ namespace Aki.SinglePlayer.Patches.ScavMode
|
||||
var brFalseLabel = generator.DefineLabel();
|
||||
|
||||
// We build the method call for our substituted method and replace the initial method call with our own, also adding our new label
|
||||
var callCode = new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(LoadOfflineRaidScreenPatch), nameof(LoadOfflineRaidScreenForScav))) { labels = { brFalseLabel }};
|
||||
var callCode = new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(LoadOfflineRaidScreenPatch), nameof(LoadOfflineRaidScreenForScav))) { labels = { brFalseLabel } };
|
||||
codes[onReadyScreenMethodIndex] = callCode;
|
||||
|
||||
// We build a new brfalse instruction and give it our new label, then replace the original brfalse instruction
|
||||
@ -114,14 +118,14 @@ namespace Aki.SinglePlayer.Patches.ScavMode
|
||||
|
||||
// Get fields from MainMenuController.cs
|
||||
var raidSettings = Traverse.Create(menuController).Field("raidSettings_0").GetValue<RaidSettings>();
|
||||
var matchmakerPlayersController = Traverse.Create(menuController).Field("gclass3030_0").GetValue<GClass3030>();
|
||||
var matchmakerPlayersController = Traverse.Create(menuController).Field($"{nameof(GClass2952).ToLowerInvariant()}_0").GetValue<GClass2952>();
|
||||
|
||||
var gclass = new MatchmakerOfflineRaidScreen.GClass3019(profile?.Info, ref raidSettings, matchmakerPlayersController);
|
||||
var gclass = new MatchmakerOfflineRaidScreen.GClass2941(profile?.Info, ref raidSettings, matchmakerPlayersController);
|
||||
|
||||
gclass.OnShowNextScreen += LoadOfflineRaidNextScreen;
|
||||
|
||||
// Ready method
|
||||
gclass.OnShowReadyScreen += (OfflineRaidAction)Delegate.CreateDelegate(typeof(OfflineRaidAction), menuController, "method_67");
|
||||
// `MatchmakerOfflineRaidScreen` OnShowReadyScreen
|
||||
gclass.OnShowReadyScreen += (OfflineRaidAction)Delegate.CreateDelegate(typeof(OfflineRaidAction), menuController, "method_70");
|
||||
gclass.ShowScreen(EScreenState.Queued);
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ using Aki.Reflection.Utils;
|
||||
using Aki.SinglePlayer.Models.Healing;
|
||||
using System.Linq;
|
||||
using BepInEx.Logging;
|
||||
using EFT.HealthSystem;
|
||||
|
||||
namespace Aki.SinglePlayer.Utils.Healing
|
||||
{
|
||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user