Skip to content

Commit

Permalink
Merge pull request #9 from dwesterwick/RestoreExcludedLayers
Browse files Browse the repository at this point in the history
Allow Removed Layers to Be Restored during a Raid
  • Loading branch information
DrakiaXYZ authored Oct 1, 2024
2 parents b11cabe + c91dde1 commit 037bb85
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 89 deletions.
5 changes: 3 additions & 2 deletions BigBrainPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace DrakiaXYZ.BigBrain
{
[BepInPlugin("xyz.drakia.bigbrain", "DrakiaXYZ-BigBrain", "1.0.1")]
[BepInPlugin("xyz.drakia.bigbrain", "DrakiaXYZ-BigBrain", "1.1.0")]
[BepInDependency("com.SPT.core", "3.9.0")]
internal class BigBrainPlugin : BaseUnityPlugin
{
Expand All @@ -27,7 +27,8 @@ private void Awake()
new BotAgentUpdatePatch().Enable();

new BotBaseBrainActivateLayerPatch().Enable();
new BotBaseBrainAddLayerPatch().Enable();

new BotStandartBotBrainActivatePatch().Enable();
}
catch (Exception ex)
{
Expand Down
114 changes: 110 additions & 4 deletions Brains/BrainManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using DrakiaXYZ.BigBrain.Internal;
using BepInEx.Logging;
using DrakiaXYZ.BigBrain.Internal;
using EFT;
using HarmonyLib;
using System;
Expand Down Expand Up @@ -36,11 +37,14 @@ internal static BrainManager Instance
internal Dictionary<Type, int> CustomLogics = new Dictionary<Type, int>();
internal List<Type> CustomLogicList = new List<Type>();
internal List<ExcludeLayerInfo> ExcludeLayers = new List<ExcludeLayerInfo>();
internal Dictionary<IPlayer, BotOwner> ActivatedBots = new Dictionary<IPlayer, BotOwner>();
internal List<ExcludedLayerInfo> ExcludedLayers = new List<ExcludedLayerInfo>();

// Allow modders to access read-only collections of the brain layers added/removed and custom logics used by bots
public static IReadOnlyDictionary<int, LayerInfo> CustomLayersReadOnly => Instance.CustomLayers.ToDictionary(i => i.Key, i => i.Value);
public static IReadOnlyDictionary<Type, int> CustomLogicsReadOnly => Instance.CustomLogics.ToDictionary(i => i.Key, i => i.Value);
public static IReadOnlyList<ExcludeLayerInfo> ExcludeLayersReadOnly => Instance.ExcludeLayers.AsReadOnly();
public static int ExcludedLayerCount => Instance.ExcludeLayers.Count;

private static FieldInfo _strategyField = Utils.GetFieldByType(typeof(AICoreLogicAgentClass), typeof(AICoreStrategyAbstractClass<>));

Expand All @@ -53,7 +57,7 @@ public class LayerInfo
public int customLayerPriority { get; private set; }
public int customLayerId { get; private set; }

private List<string> _customLayerBrains;
internal List<string> _customLayerBrains;

public IReadOnlyList<string> CustomLayerBrains => _customLayerBrains.AsReadOnly();

Expand All @@ -70,7 +74,7 @@ public class ExcludeLayerInfo
{
public string excludeLayerName { get; private set; }

private List<string> _excludeLayerBrains;
internal List<string> _excludeLayerBrains;

public IReadOnlyList<string> ExcludeLayerBrains => _excludeLayerBrains.AsReadOnly();

Expand All @@ -81,6 +85,25 @@ public ExcludeLayerInfo(string layerName, List<string> brains)
}
}

internal class ExcludedLayerInfo
{
public BotOwner BotOwner { get; private set; }
public AICoreLogicLayerClass Layer { get; private set; }
public string BrainName { get; private set; }
public string LayerName { get; private set; }
public int Index { get; private set; }

public ExcludedLayerInfo(BotOwner botOwner, AICoreLogicLayerClass layer, string brainName, int index)
{
BotOwner = botOwner;
Layer = layer;
BrainName = brainName;
Index = index;

LayerName = layer.Name();
}
}

public static int AddCustomLayer(Type customLayerType, List<string> brainNames, int customLayerPriority)
{
if (!typeof(CustomLayer).IsAssignableFrom(customLayerType))
Expand All @@ -105,14 +128,97 @@ public static void AddCustomLayers(List<Type> customLayerTypes, List<string> bra

public static void RemoveLayer(string layerName, List<string> brainNames)
{
Instance.ExcludeLayers.Add(new ExcludeLayerInfo(layerName, brainNames));
ExcludeLayerInfo matchingExcludeLayerInfo = null;

// Add new brain names to an existing ExcludeLayerInfo if one exists
foreach (ExcludeLayerInfo excludeLayerInfo in Instance.ExcludeLayers)
{
if (excludeLayerInfo.excludeLayerName != layerName)
{
continue;
}

matchingExcludeLayerInfo = excludeLayerInfo;

// Ensure duplicate brain names are not added
IEnumerable<string> additionalBrainNames = brainNames.Where(x => !matchingExcludeLayerInfo._excludeLayerBrains.Contains(x));
matchingExcludeLayerInfo._excludeLayerBrains.AddRange(additionalBrainNames);

break;
}

// If a matching ExcludeLayerInfo wasn't found, create a new one
if (matchingExcludeLayerInfo == null)
{
matchingExcludeLayerInfo = new ExcludeLayerInfo(layerName, brainNames);
Instance.ExcludeLayers.Add(matchingExcludeLayerInfo);
}

// Remove the layer for all bots that have already spawned
foreach (BotOwner botOwner in Instance.ActivatedBots.Values)
{
if ((botOwner == null) || botOwner.IsDead)
{
continue;
}

botOwner.RemoveLayerForBot(matchingExcludeLayerInfo);
}
}

public static void RemoveLayers(List<string> layerNames, List<string> brainNames)
{
layerNames.ForEach(layerName => RemoveLayer(layerName, brainNames));
}

public static void RestoreLayer(string layerName, List<string> brainNames)
{
List<ExcludeLayerInfo> excludeLayerInfosToRemove = new List<ExcludeLayerInfo>();

// Remove the combination of layer name and brain name(s) from ExcludeLayers to ensure they aren't removed from bots that haven't spawned yet
foreach (ExcludeLayerInfo excludeLayer in Instance.ExcludeLayers)
{
if (excludeLayer.excludeLayerName != layerName)
{
continue;
}

excludeLayer._excludeLayerBrains.RemoveAll(x => brainNames.Contains(x));

if (excludeLayer._excludeLayerBrains.Count == 0)
{
excludeLayerInfosToRemove.Add(excludeLayer);
}
}

// If all brain names have been removed from a ExcludeLayer entry, remove it from the list
foreach (ExcludeLayerInfo excludeLayerInfoToRemove in excludeLayerInfosToRemove)
{
Instance.ExcludeLayers.Remove(excludeLayerInfoToRemove);
}

// Restore the layer for all applicable bots that have already spawned
foreach (BotOwner botOwner in Instance.ActivatedBots.Values)
{
if ((botOwner == null) || botOwner.IsDead)
{
continue;
}

if (!brainNames.Contains(botOwner.Brain.BaseBrain.ShortName()))
{
continue;
}

botOwner.RestoreLayerForBot(layerName);
}
}

public static void RestoreLayers(List<string> layerNames, List<string> brainNames)
{
layerNames.ForEach(layerName => RestoreLayer(layerName, brainNames));
}

public static bool IsCustomLayerActive(BotOwner botOwner)
{
object activeLayer = GetActiveLayer(botOwner);
Expand Down
9 changes: 7 additions & 2 deletions DrakiaXYZ-BigBrain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
<Reference Include="0Harmony">
<HintPath>..\..\BepInEx\core\0Harmony.dll</HintPath>
</Reference>
<Reference Include="DissonanceVoip, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\EscapeFromTarkov_Data\Managed\DissonanceVoip.dll</HintPath>
</Reference>
<Reference Include="spt-common">
<HintPath>..\..\BepInEx\plugins\spt\spt-common.dll</HintPath>
</Reference>
Expand Down Expand Up @@ -97,15 +101,16 @@
<Compile Include="Brains\CustomBrain.cs" />
<Compile Include="Brains\CustomLayer.cs" />
<Compile Include="Brains\CustomLogic.cs" />
<Compile Include="Internal\BrainHelpers.cs" />
<Compile Include="Internal\CustomLayerWrapper.cs" />
<Compile Include="Internal\CustomLogicWrapper.cs" />
<None Include="LICENSE" />
<Compile Include="Patches\BotAgentUpdatePatch.cs" />
<Compile Include="Patches\BotBaseBrainActivateLayerPatch.cs" />
<Compile Include="Patches\BotBaseBrainActivatePatch.cs" />
<Compile Include="Patches\BotBaseBrainAddLayerPatch.cs" />
<Compile Include="Patches\BotBaseBrainUpdatePatch.cs" />
<Compile Include="Patches\BotBrainCreateLogicNodePatch.cs" />
<Compile Include="Patches\BotStandartBotBrainActivatePatch.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils.cs" />
<Compile Include="VersionChecker\TarkovVersion.cs" />
Expand All @@ -121,7 +126,7 @@
<PropertyGroup>
<PostBuildEvent>copy "$(TargetPath)" "$(ProjectDir)\..\..\BepInEx\plugins\$(TargetFileName)"
if $(ConfigurationName) == Package (
powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(ProjectDir)\package.ps1
powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(ProjectDir)\package.ps1
)</PostBuildEvent>
</PropertyGroup>
</Project>
125 changes: 125 additions & 0 deletions Internal/BrainHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using BepInEx.Logging;
using DrakiaXYZ.BigBrain.Brains;
using EFT;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

using AICoreLogicLayerClass = AICoreLayerClass<BotLogicDecision>;

namespace DrakiaXYZ.BigBrain.Internal
{
internal static class BrainHelpers
{
private static FieldInfo _layerDictionary = null;

public static Dictionary<int, AICoreLogicLayerClass> GetBrainLayerDictionary(this BaseBrain brain)
{
if (_layerDictionary == null)
{
Type baseBrainType = typeof(BaseBrain);
Type aiCoreStrategyType = baseBrainType.BaseType;

_layerDictionary = AccessTools.Field(aiCoreStrategyType, "dictionary_0");
}

return _layerDictionary.GetValue(brain) as Dictionary<int, AICoreLogicLayerClass>;
}

internal static void RemoveAllExcludedLayers(this BotOwner botOwner)
{
foreach (BrainManager.ExcludeLayerInfo excludeLayer in BrainManager.Instance.ExcludeLayers)
{
botOwner.RemoveLayerForBot(excludeLayer);
}
}

internal static void RemoveLayerForBot(this BotOwner botOwner, BrainManager.ExcludeLayerInfo excludeLayer)
{
// Only remove the layer if ExcludeLayer contains the bot's brain name
if (!excludeLayer.ExcludeLayerBrains.Contains(botOwner.Brain.BaseBrain.ShortName()))
{
return;
}

botOwner.RemoveLayerForBot(excludeLayer.excludeLayerName);
}

internal static void RemoveLayerForBot(this BotOwner botOwner, string layerName)
{
// Get all brain layers the bot currently has
Dictionary<int, AICoreLogicLayerClass> botBrainLayerDictionary = botOwner.Brain.BaseBrain.GetBrainLayerDictionary();

int layerIndexToRemove = -1;

foreach (int index in botBrainLayerDictionary.Keys)
{
if (botBrainLayerDictionary[index].Name() != layerName)
{
continue;
}

// Remove the brain layer from the bot's brain
layerIndexToRemove = index;
botOwner.Brain.BaseBrain.method_3(index);

// Ensure there is no longer a brain layer of the same type in the bot's brain
if (botOwner.Brain.BaseBrain.method_2(botBrainLayerDictionary[index]))
{
throw new InvalidOperationException($"Could not remove brain layer '{layerName}' from {botOwner.name}");
}

// Cache the removed layer so it can be restored later if needed
BrainManager.Instance.ExcludedLayers.Add(new BrainManager.ExcludedLayerInfo(botOwner, botBrainLayerDictionary[index], botOwner.Brain.BaseBrain.ShortName(), index));

break;
}

// If a matching layer was found, remove it from the bot's brain-layer dictionary. This is not done in method_3.
if (layerIndexToRemove > -1)
{
botBrainLayerDictionary.Remove(layerIndexToRemove);
}
}

internal static void RestoreLayerForBot(this BotOwner botOwner, string layerName)
{
// Get all brain layers the bot currently has
Dictionary<int, AICoreLogicLayerClass> botBrainLayerDictionary = botOwner.Brain.BaseBrain.GetBrainLayerDictionary();

List<BrainManager.ExcludedLayerInfo> restoredLayers = new List<BrainManager.ExcludedLayerInfo>();

foreach (BrainManager.ExcludedLayerInfo excludedLayer in BrainManager.Instance.ExcludedLayers)
{
if ((excludedLayer.BotOwner != botOwner) || (excludedLayer.LayerName != layerName))
{
continue;
}

// Ensure the brain-layer index doesn't already exist in the bot's brain
if (botBrainLayerDictionary.ContainsKey(excludedLayer.Index))
{
throw new InvalidOperationException($"Cannot restore '{excludedLayer.LayerName}' for {botOwner.name}. Index already exists in bot brain.");
}

// Add the brain layer back to the bot's brain
if (!botOwner.Brain.BaseBrain.method_0(excludedLayer.Index, excludedLayer.Layer, true))
{
throw new InvalidOperationException($"Cannot restore '{excludedLayer.LayerName}' for {botOwner.name}. Failed to add layer.");
}

restoredLayers.Add(excludedLayer);
}

// After a brain layer has been restored, the cached version of it can be removed
foreach (BrainManager.ExcludedLayerInfo restoredLayer in restoredLayers)
{
BrainManager.Instance.ExcludedLayers.Remove(restoredLayer);
}
}
}
}
Loading

0 comments on commit 037bb85

Please sign in to comment.