using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Text.Json;
using BoneLib.BoneMenu;
using MelonLoader;
using Microsoft.CodeAnalysis;
using MimicMod;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Mod), "Mimic Mod", "0.5.0", "Eryk", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("MimicMod")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("MimicMod")]
[assembly: AssemblyTitle("MimicMod")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace MimicMod
{
internal sealed class MimicAI : IDisposable
{
private sealed class BotRuntime
{
public float NextAttackAt;
public float AttackModeUntil;
public float AttackWindupUntil;
public bool AttackPending;
public float NextCoverAt;
public Vector3 LastGoal;
public bool HasGoal;
public bool EncounterRecorded;
public float NextRushRecordAt;
public MimicBehavior Behavior = new MimicBehavior();
public float RestoreVisualAt;
public Renderer[] FlickerRenderers;
public bool[] RendererStates;
}
private readonly Instance _log;
private MimicSettings _settings = new MimicSettings();
private MimicLearning _learning;
private MimicEncounters _encounters;
private float _nextJumpscareAt;
private float _nextLearningSampleAt;
private readonly ArrayList _bots = new ArrayList();
private readonly Dictionary<object, BotRuntime> _runtime = new Dictionary<object, BotRuntime>();
private readonly List<GameObject> _goalObjects = new List<GameObject>();
private readonly Dictionary<object, GameObject> _goalMarkers = new Dictionary<object, GameObject>();
private Type _managerType;
private PropertyInfo _allProperty;
private bool _enabled;
private bool _selfAware;
private bool _coverEnabled = true;
private bool _attacksEnabled = true;
private bool _visualGlitches;
private bool _disposed;
private bool _coverWarningLogged;
private float _nextRefresh;
private float _nextDecision;
private float _nextGlitch;
private float _sceneRefreshUntil;
private int _decision;
private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
private const float AttackRange = 2.15f;
private const float AttackWindup = 0.55f;
private const float CoverRadius = 5.5f;
public MimicAI(Instance logger)
{
_log = logger;
ResolveDoppel();
}
public void NotifySceneLoaded(string sceneName)
{
_bots.Clear();
_runtime.Clear();
_goalMarkers.Clear();
for (int i = 0; i < _goalObjects.Count; i++)
{
try
{
if ((Object)(object)_goalObjects[i] != (Object)null)
{
Object.Destroy((Object)(object)_goalObjects[i]);
}
}
catch
{
}
}
_goalObjects.Clear();
_managerType = null;
_allProperty = null;
_nextRefresh = 0f;
_nextDecision = 0f;
_nextLearningSampleAt = 0f;
_sceneRefreshUntil = Time.time + 30f;
_log.Msg("Mimic detected level load/reload ('" + (sceneName ?? "unknown") + "'); refreshing Doppel bots.");
}
public void Configure(MimicSettings settings, MimicLearning learning, MimicEncounters encounters)
{
_settings = settings ?? new MimicSettings();
_settings.ClampValues();
_learning = learning;
_encounters = encounters;
_selfAware = _settings.selfAwareEnabled;
_coverEnabled = _settings.stalkingEnabled;
_attacksEnabled = _settings.attacksEnabled && _settings.behaviorMode != MimicBehaviorMode.NonHostile && _settings.behaviorMode != MimicBehaviorMode.Jumpscare;
_visualGlitches = _settings.visualGlitchesEnabled || _settings.behaviorMode == MimicBehaviorMode.Jumpscare;
}
public void SetEnabled(bool enabled)
{
if (_enabled != enabled)
{
_enabled = enabled;
if (!enabled)
{
RestoreBots();
}
else
{
_nextRefresh = 0f;
_sceneRefreshUntil = Time.time + 30f;
RefreshBots();
_nextDecision = Time.time + 0.5f;
}
_log.Msg("Mimic AI " + (enabled ? "enabled." : "disabled.") + ".");
}
}
public void SetSelfAware(bool enabled)
{
_selfAware = enabled;
_log.Msg("Experimental self-aware behavior " + (enabled ? "enabled." : "disabled.") + ".");
}
public void SetCoverEnabled(bool enabled)
{
_coverEnabled = enabled;
_log.Msg("Cover seeking and stalking " + (enabled ? "enabled." : "disabled.") + ".");
}
public void SetAttacksEnabled(bool enabled)
{
_attacksEnabled = enabled;
_log.Msg("Close-range attacks " + (enabled ? "enabled." : "disabled.") + ".");
}
public void SetVisualGlitches(bool enabled)
{
_visualGlitches = enabled;
if (!enabled)
{
RestoreAllFlickers();
}
_log.Msg("Visual glitches " + (enabled ? "enabled." : "disabled.") + ".");
}
public void RefreshBots()
{
RefreshBots(logResult: true);
}
private void RefreshBots(bool logResult)
{
ResolveDoppel();
_bots.Clear();
if (_allProperty == null)
{
if (logResult)
{
_log.Warning("Doppel BotManager.All was not found. Ensure Doppel is installed and loaded.");
}
return;
}
try
{
if (_allProperty.GetValue(null, null) is IEnumerable enumerable)
{
foreach (object item in enumerable)
{
if (item != null)
{
_bots.Add(item);
if (!_runtime.ContainsKey(item))
{
_runtime[item] = new BotRuntime();
}
}
}
}
List<object> list = null;
foreach (object key in _runtime.Keys)
{
bool flag = false;
for (int i = 0; i < _bots.Count; i++)
{
if (key == _bots[i] || object.Equals(key, _bots[i]))
{
flag = true;
break;
}
}
if (!flag)
{
if (list == null)
{
list = new List<object>();
}
list.Add(key);
}
}
if (list != null)
{
for (int j = 0; j < list.Count; j++)
{
_runtime.Remove(list[j]);
}
}
if (logResult)
{
_log.Msg("Mimic found " + _bots.Count + " existing Doppel bot(s).");
}
}
catch (Exception ex)
{
if (logResult)
{
_log.Error("Doppel bot refresh failed: " + ex.Message);
}
}
}
private void ResolveDoppel()
{
if (_managerType != null && _allProperty != null)
{
return;
}
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
try
{
Type type = assembly.GetType("ThirdEyeBonelabMod.Bots.BotManager", throwOnError: false);
if (type == null)
{
continue;
}
_managerType = type;
_allProperty = type.GetProperty("All", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
break;
}
catch
{
}
}
}
public void Update()
{
//IL_0142: Unknown result type (might be due to invalid IL or missing references)
//IL_0148: Unknown result type (might be due to invalid IL or missing references)
//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
//IL_03b4: Unknown result type (might be due to invalid IL or missing references)
//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
//IL_0704: Unknown result type (might be due to invalid IL or missing references)
//IL_070a: Unknown result type (might be due to invalid IL or missing references)
//IL_049f: Unknown result type (might be due to invalid IL or missing references)
//IL_071b: Unknown result type (might be due to invalid IL or missing references)
//IL_04e1: Unknown result type (might be due to invalid IL or missing references)
//IL_04e7: Unknown result type (might be due to invalid IL or missing references)
//IL_0726: Unknown result type (might be due to invalid IL or missing references)
//IL_0728: Unknown result type (might be due to invalid IL or missing references)
//IL_04f8: Unknown result type (might be due to invalid IL or missing references)
//IL_0503: Unknown result type (might be due to invalid IL or missing references)
//IL_0505: Unknown result type (might be due to invalid IL or missing references)
if (_disposed)
{
return;
}
UpdateFlickerRestores();
if (!_enabled)
{
return;
}
if (Time.time >= _nextRefresh)
{
bool flag = Time.time < _sceneRefreshUntil;
_nextRefresh = Time.time + (flag ? 1f : 8f);
RefreshBots(logResult: false);
if (_bots.Count > 0)
{
_sceneRefreshUntil = 0f;
_nextRefresh = Time.time + 8f;
}
}
if (_visualGlitches && Time.time >= _nextGlitch)
{
_nextGlitch = Time.time + Random.Range(8f, 18f);
TryStartRandomFlicker();
}
if (Time.time < _nextDecision || _bots.Count == 0)
{
return;
}
_nextDecision = Time.time + 0.35f;
Transform val = (((Object)(object)Camera.main != (Object)null) ? ((Component)Camera.main).transform : null);
if ((Object)(object)val == (Object)null)
{
return;
}
MimicLearning mimicLearning = (_settings.learningEnabled ? _learning : null);
if (mimicLearning != null && Time.time >= _nextLearningSampleAt)
{
_nextLearningSampleAt = Time.time + 0.4f;
mimicLearning.Observe(val.position, val.forward, Time.time);
}
int num = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
for (int num5 = _bots.Count - 1; num5 >= 0; num5--)
{
object obj = _bots[num5];
if (obj == null)
{
_bots.RemoveAt(num5);
continue;
}
BotRuntime runtime = GetRuntime(obj);
Transform botTransform = GetBotTransform(obj);
if ((Object)(object)botTransform == (Object)null)
{
SetModeIfDifferent(obj, "Brain");
continue;
}
float num6 = Vector3.Distance(botTransform.position, val.position);
bool flag2 = IsPlayerLookingAt(val, botTransform.position, num6) && HasLineOfSight(val.position, botTransform.position);
if (_encounters != null && num6 < 20f && !runtime.EncounterRecorded)
{
_encounters.Record("encounter", "Mimic entered player proximity; distance=" + num6.ToString("F1"));
runtime.EncounterRecorded = true;
}
if (num6 > 25f)
{
runtime.EncounterRecorded = false;
}
if (_settings.behaviorMode == MimicBehaviorMode.Jumpscare)
{
if (num6 <= 5f && flag2 && Time.time >= _nextJumpscareAt)
{
_nextJumpscareAt = Time.time + _settings.jumpscareCooldown;
if (_visualGlitches)
{
TryStartFlickerForBot(obj);
}
if (_encounters != null)
{
_encounters.Record("jumpscare", "Close-range jumpscare trigger; recorded audio feature is disabled.");
}
}
if (num6 > 2.15f)
{
if (!TrySetRushMode(obj))
{
SetModeIfDifferent(obj, "Follow");
}
}
else
{
SetModeIfDifferent(obj, "Stand");
}
continue;
}
if (_settings.behaviorMode == MimicBehaviorMode.NonHostile)
{
runtime.AttackPending = false;
runtime.AttackModeUntil = 0f;
if (!SetModeIfDifferent(obj, "Wander"))
{
SetModeIfDifferent(obj, "Stand");
}
continue;
}
if (_settings.behaviorMode == MimicBehaviorMode.RuthlessAttacker && num6 > 2.15f)
{
if (!TrySetRushMode(obj))
{
SetModeIfDifferent(obj, "Follow");
}
continue;
}
if (_settings.behaviorMode == MimicBehaviorMode.AdaptiveStalker)
{
MimicAction mimicAction = runtime.Behavior.Decide(_settings, mimicLearning, Time.time, num6, flag2, HasLineOfSight(val.position, botTransform.position), mimicLearning != null && mimicLearning.AverageSpeed() > 0.12f, num6 <= 2.15f, Time.time >= _nextJumpscareAt, _encounters);
if (_coverEnabled && mimicAction == MimicAction.Rush)
{
if (!TrySetRushMode(obj))
{
SetModeIfDifferent(obj, "Follow");
}
if (_encounters != null && Time.time >= runtime.NextRushRecordAt)
{
_encounters.Record("rush", "Adaptive behavior selected a rush.");
runtime.NextRushRecordAt = Time.time + 5f;
}
continue;
}
if (_coverEnabled && mimicAction == MimicAction.Stalk)
{
SetModeIfDifferent(obj, "Follow");
continue;
}
if (_coverEnabled && mimicAction == MimicAction.Investigate)
{
if (TrySetGoal(obj, val.position))
{
SetModeIfDifferent(obj, "Brain");
}
else
{
SetModeIfDifferent(obj, "Follow");
}
continue;
}
if (mimicAction == MimicAction.TakeCover && _coverEnabled && TryFindCover(botTransform.position, val.position, out var result) && TrySetGoal(obj, result))
{
runtime.LastGoal = result;
runtime.HasGoal = true;
SetModeIfDifferent(obj, "Brain");
continue;
}
}
if (_selfAware && flag2 && num6 < 20f)
{
SetModeIfDifferent(obj, "Stand");
num4++;
continue;
}
if (_attacksEnabled && num6 <= 2.15f)
{
if (runtime.AttackPending)
{
if (Time.time < runtime.AttackWindupUntil)
{
SetModeIfDifferent(obj, "Stand");
continue;
}
runtime.AttackPending = false;
runtime.AttackModeUntil = Time.time + 0.8f;
SetModeIfDifferent(obj, "Fists");
if (_encounters != null)
{
_encounters.Record("attack", "Doppel Fists mode entered after wind-up.");
}
num++;
}
else if (runtime.AttackModeUntil > Time.time)
{
SetModeIfDifferent(obj, "Fists");
num++;
}
else if (Time.time >= runtime.NextAttackAt)
{
runtime.AttackPending = true;
runtime.AttackWindupUntil = Time.time + 0.55f;
runtime.NextAttackAt = Time.time + Mathf.Max(0.5f, _settings.attackCooldown);
SetModeIfDifferent(obj, "Stand");
}
else
{
SetModeIfDifferent(obj, "Brain");
}
continue;
}
if (runtime.AttackModeUntil > 0f && runtime.AttackModeUntil <= Time.time)
{
runtime.AttackModeUntil = 0f;
SetModeIfDifferent(obj, "Brain");
}
if (num6 > 2.15f && runtime.AttackPending)
{
runtime.AttackPending = false;
}
bool flag3 = false;
if (_coverEnabled && Time.time >= runtime.NextCoverAt)
{
runtime.NextCoverAt = Time.time + Random.Range(2.8f, 4.8f);
if (TryFindCover(botTransform.position, val.position, out var result2) && TrySetGoal(obj, result2))
{
runtime.LastGoal = result2;
runtime.HasGoal = true;
if (_encounters != null)
{
_encounters.Record("cover", "Assigned cover goal.");
}
SetModeIfDifferent(obj, "Brain");
num2++;
flag3 = true;
}
}
if (!flag3)
{
int num7 = (_decision + num5) % 9;
string modeName = ((!_coverEnabled) ? "Brain" : (num7 switch
{
5 => "Follow",
0 => "Wander",
_ => "Brain",
}));
SetModeIfDifferent(obj, modeName);
num3++;
}
}
_decision++;
if (num > 0)
{
_log.Msg("Mimic: close-range Doppel combat active for " + num + " bot(s).");
}
else if (num2 > 0)
{
_log.Msg("Mimic: assigned physics-tested cover goal to " + num2 + " bot(s).");
}
else if (num4 > 0)
{
_log.Msg("Mimic self-aware behavior: pausing while observed.");
}
}
private BotRuntime GetRuntime(object bot)
{
if (!_runtime.TryGetValue(bot, out var value))
{
value = new BotRuntime();
_runtime[bot] = value;
}
return value;
}
private bool TryFindCover(Vector3 botPosition, Vector3 playerPosition, out Vector3 result)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_0089: Unknown result type (might be due to invalid IL or missing references)
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_008f: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Unknown result type (might be due to invalid IL or missing references)
//IL_0099: Unknown result type (might be due to invalid IL or missing references)
//IL_009e: Unknown result type (might be due to invalid IL or missing references)
//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
//IL_00da: Unknown result type (might be due to invalid IL or missing references)
//IL_00df: Unknown result type (might be due to invalid IL or missing references)
//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
//IL_0101: Unknown result type (might be due to invalid IL or missing references)
//IL_0106: Unknown result type (might be due to invalid IL or missing references)
//IL_010b: Unknown result type (might be due to invalid IL or missing references)
//IL_010d: Unknown result type (might be due to invalid IL or missing references)
//IL_010e: Unknown result type (might be due to invalid IL or missing references)
//IL_0118: Unknown result type (might be due to invalid IL or missing references)
//IL_011d: Unknown result type (might be due to invalid IL or missing references)
//IL_0122: Unknown result type (might be due to invalid IL or missing references)
//IL_0124: Unknown result type (might be due to invalid IL or missing references)
//IL_0129: Unknown result type (might be due to invalid IL or missing references)
//IL_013d: Unknown result type (might be due to invalid IL or missing references)
//IL_013f: Unknown result type (might be due to invalid IL or missing references)
//IL_0143: Unknown result type (might be due to invalid IL or missing references)
//IL_0155: Unknown result type (might be due to invalid IL or missing references)
//IL_0157: Unknown result type (might be due to invalid IL or missing references)
//IL_0161: Unknown result type (might be due to invalid IL or missing references)
//IL_0166: Unknown result type (might be due to invalid IL or missing references)
//IL_0179: Unknown result type (might be due to invalid IL or missing references)
//IL_017a: Unknown result type (might be due to invalid IL or missing references)
//IL_0181: Unknown result type (might be due to invalid IL or missing references)
//IL_0182: Unknown result type (might be due to invalid IL or missing references)
//IL_0198: Unknown result type (might be due to invalid IL or missing references)
//IL_0199: Unknown result type (might be due to invalid IL or missing references)
//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
result = botPosition;
Vector3 val = botPosition - playerPosition;
val.y = 0f;
if (((Vector3)(ref val)).sqrMagnitude < 0.1f)
{
val = Vector3.back;
}
((Vector3)(ref val)).Normalize();
Vector3 val2 = Vector3.Cross(Vector3.up, val);
Vector3 normalized = ((Vector3)(ref val2)).normalized;
float[] array = new float[4] { 2f, 3.2f, 4.5f, 5.5f };
float[] array2 = new float[5] { 0f, 1.3f, -1.3f, 2.4f, -2.4f };
float num = float.MaxValue;
bool result2 = false;
RaycastHit val4 = default(RaycastHit);
RaycastHit val7 = default(RaycastHit);
for (int i = 0; i < array.Length; i++)
{
for (int j = 0; j < array2.Length; j++)
{
Vector3 val3 = playerPosition + val * array[i] + normalized * array2[j];
if (!Physics.Raycast(val3 + Vector3.up * 2.2f, Vector3.down, ref val4, 5f, -1, (QueryTriggerInteraction)1))
{
continue;
}
val3 = ((RaycastHit)(ref val4)).point + Vector3.up * 0.1f;
Vector3 val5 = val3 + Vector3.up * 1.1f;
Vector3 val6 = playerPosition + Vector3.up * 1f - val5;
float magnitude = ((Vector3)(ref val6)).magnitude;
if (!(magnitude < 0.2f) && Physics.Raycast(val5, val6 / magnitude, ref val7, magnitude, -1, (QueryTriggerInteraction)1) && !Physics.CheckSphere(val3 + Vector3.up * 0.7f, 0.28f, -1, (QueryTriggerInteraction)1))
{
float num2 = Vector3.Distance(botPosition, val3) + Vector3.Distance(playerPosition, val3) * 0.25f;
if (num2 < num && Vector3.Distance(botPosition, val3) > 1f)
{
num = num2;
result = val3;
result2 = true;
}
}
}
}
return result2;
}
private static bool HasLineOfSight(Vector3 from, Vector3 to)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
Vector3 val = to - from;
float magnitude = ((Vector3)(ref val)).magnitude;
if (magnitude < 0.1f)
{
return true;
}
RaycastHit val2 = default(RaycastHit);
return !Physics.Raycast(from, val / magnitude, ref val2, magnitude, -1, (QueryTriggerInteraction)1);
}
private static bool IsPlayerLookingAt(Transform player, Vector3 target, float distance)
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
if (distance > 20f)
{
return false;
}
Vector3 val = target + Vector3.up * 0.8f - player.position;
if (((Vector3)(ref val)).sqrMagnitude < 0.001f)
{
return true;
}
return Vector3.Dot(player.forward, ((Vector3)(ref val)).normalized) > 0.84f;
}
private bool TrySetGoal(object bot, Vector3 goal)
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
try
{
Type type = bot.GetType();
FieldInfo field = type.GetField("goal", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null && TryConvertGoal(bot, field.FieldType, goal, out var value))
{
field.SetValue(bot, value);
return true;
}
PropertyInfo property = type.GetProperty("goal", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null && property.CanWrite && TryConvertGoal(bot, property.PropertyType, goal, out var value2))
{
property.SetValue(bot, value2, null);
return true;
}
}
catch (Exception ex)
{
if (!_coverWarningLogged)
{
_log.Warning("Doppel goal assignment failed; using built-in AI modes instead. " + ex.Message);
_coverWarningLogged = true;
}
}
if (!_coverWarningLogged)
{
_log.Warning("This Doppel build does not expose a writable compatible 'goal' member. Cover positions can be detected, but custom goal navigation is unavailable; using Doppel Brain/Wander instead.");
_coverWarningLogged = true;
}
return false;
}
private bool TryConvertGoal(object bot, Type targetType, Vector3 goal, out object value)
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Unknown result type (might be due to invalid IL or missing references)
//IL_0069: Expected O, but got Unknown
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
value = null;
if (targetType == typeof(Vector3))
{
value = goal;
return true;
}
if (targetType == typeof(Transform) || targetType == typeof(GameObject))
{
if (!_goalMarkers.TryGetValue(bot, out var value2) || (Object)(object)value2 == (Object)null)
{
value2 = new GameObject("MimicMod_CoverGoal");
((Object)value2).hideFlags = (HideFlags)61;
_goalMarkers[bot] = value2;
_goalObjects.Add(value2);
}
value2.transform.position = goal;
value = ((targetType == typeof(Transform)) ? ((object)value2.transform) : ((object)value2));
return true;
}
return false;
}
private static Transform GetBotTransform(object bot)
{
try
{
Component val = (Component)((bot is Component) ? bot : null);
if (val != null)
{
return val.transform;
}
GameObject val2 = (GameObject)((bot is GameObject) ? bot : null);
if (val2 != null)
{
return val2.transform;
}
Type type = bot.GetType();
string[] array = new string[3] { "rig", "transform", "gameObject" };
foreach (string name in array)
{
object obj = ReadMember(type, bot, name);
Transform val3 = AsTransform(obj);
if ((Object)(object)val3 != (Object)null)
{
return val3;
}
if (obj != null)
{
Transform val4 = AsTransform(ReadMember(obj.GetType(), obj, "transform"));
if ((Object)(object)val4 != (Object)null)
{
return val4;
}
}
}
}
catch
{
}
return null;
}
private static object ReadMember(Type type, object instance, string name)
{
FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
return field.GetValue(instance);
}
PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null && property.GetIndexParameters().Length == 0)
{
return property.GetValue(instance, null);
}
return null;
}
private static Transform AsTransform(object value)
{
Transform val = (Transform)((value is Transform) ? value : null);
if (val != null)
{
return val;
}
Component val2 = (Component)((value is Component) ? value : null);
if (val2 != null)
{
return val2.transform;
}
GameObject val3 = (GameObject)((value is GameObject) ? value : null);
if (val3 != null)
{
return val3.transform;
}
return null;
}
private bool SetModeIfDifferent(object bot, string modeName)
{
try
{
Type type = bot.GetType();
MethodInfo method = type.GetMethod("SetMode", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (method == null)
{
return false;
}
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length != 1 || !parameters[0].ParameterType.IsEnum)
{
return false;
}
object obj = Enum.Parse(parameters[0].ParameterType, modeName, ignoreCase: true);
object obj2 = ReadMember(type, bot, "mode");
if (obj2 != null && obj2.Equals(obj))
{
return true;
}
method.Invoke(bot, new object[1] { obj });
return true;
}
catch
{
return false;
}
}
private bool TrySetRushMode(object bot)
{
if (!SetModeIfDifferent(bot, "Run") && !SetModeIfDifferent(bot, "Sprint") && !SetModeIfDifferent(bot, "Chase"))
{
return SetModeIfDifferent(bot, "Follow");
}
return true;
}
private void TryStartFlickerForBot(object bot)
{
Transform val = ((bot == null) ? null : GetBotTransform(bot));
if ((Object)(object)val == (Object)null)
{
return;
}
Renderer[] componentsInChildren = ((Component)val).GetComponentsInChildren<Renderer>(true);
if (componentsInChildren == null || componentsInChildren.Length == 0)
{
return;
}
BotRuntime runtime = GetRuntime(bot);
if (runtime.RestoreVisualAt > Time.time)
{
return;
}
runtime.FlickerRenderers = componentsInChildren;
runtime.RendererStates = new bool[componentsInChildren.Length];
for (int i = 0; i < componentsInChildren.Length; i++)
{
runtime.RendererStates[i] = (Object)(object)componentsInChildren[i] != (Object)null && componentsInChildren[i].enabled;
if ((Object)(object)componentsInChildren[i] != (Object)null)
{
componentsInChildren[i].enabled = false;
}
}
runtime.RestoreVisualAt = Time.time + Random.Range(0.08f, 0.16f);
}
private void TryStartRandomFlicker()
{
if (_bots.Count == 0)
{
return;
}
for (int i = 0; i < _bots.Count; i++)
{
object obj = _bots[Random.Range(0, _bots.Count)];
Transform val = ((obj == null) ? null : GetBotTransform(obj));
if ((Object)(object)val == (Object)null)
{
continue;
}
Renderer[] componentsInChildren = ((Component)val).GetComponentsInChildren<Renderer>(true);
if (componentsInChildren == null || componentsInChildren.Length == 0)
{
break;
}
BotRuntime runtime = GetRuntime(obj);
if (runtime.RestoreVisualAt > Time.time)
{
break;
}
runtime.FlickerRenderers = componentsInChildren;
runtime.RendererStates = new bool[componentsInChildren.Length];
for (int j = 0; j < componentsInChildren.Length; j++)
{
runtime.RendererStates[j] = (Object)(object)componentsInChildren[j] != (Object)null && componentsInChildren[j].enabled;
if ((Object)(object)componentsInChildren[j] != (Object)null)
{
componentsInChildren[j].enabled = false;
}
}
runtime.RestoreVisualAt = Time.time + Random.Range(0.07f, 0.14f);
break;
}
}
private void UpdateFlickerRestores()
{
foreach (KeyValuePair<object, BotRuntime> item in _runtime)
{
BotRuntime value = item.Value;
if (!(value.RestoreVisualAt <= 0f) && !(Time.time < value.RestoreVisualAt))
{
RestoreFlicker(value);
}
}
}
private void RestoreFlicker(BotRuntime state)
{
if (state.FlickerRenderers != null && state.RendererStates != null)
{
for (int i = 0; i < state.FlickerRenderers.Length && i < state.RendererStates.Length; i++)
{
if ((Object)(object)state.FlickerRenderers[i] != (Object)null)
{
state.FlickerRenderers[i].enabled = state.RendererStates[i];
}
}
}
state.FlickerRenderers = null;
state.RendererStates = null;
state.RestoreVisualAt = 0f;
}
private void RestoreAllFlickers()
{
foreach (BotRuntime value in _runtime.Values)
{
RestoreFlicker(value);
}
}
private void RestoreBots()
{
RestoreAllFlickers();
foreach (object bot in _bots)
{
if (bot != null)
{
SetModeIfDifferent(bot, "Stand");
}
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
RestoreBots();
_bots.Clear();
foreach (GameObject goalObject in _goalObjects)
{
if ((Object)(object)goalObject != (Object)null)
{
Object.Destroy((Object)(object)goalObject);
}
}
_goalObjects.Clear();
_goalMarkers.Clear();
_runtime.Clear();
_log.Msg("Mimic AI disposed.");
}
}
internal sealed class MimicAudio : IDisposable
{
private sealed class MicDevice
{
public uint Id;
public string Name;
}
private sealed class WinMicCapture : IDisposable
{
private const uint CALLBACK_FUNCTION = 196608u;
private const uint WIM_DATA = 960u;
private const int BufferCount = 3;
private const int BufferBytes = 4096;
private readonly uint _deviceId;
private readonly int _sampleRate;
private readonly Instance _log;
private readonly object _sync = new object();
private readonly List<byte> _data = new List<byte>();
private readonly NativeWaveIn.WaveInProc _callback;
private readonly List<IntPtr> _headers = new List<IntPtr>();
private IntPtr _handle;
private bool _capturing;
private bool _closed;
public WinMicCapture(uint deviceId, int sampleRate, Instance logger)
{
_deviceId = deviceId;
_sampleRate = sampleRate;
_log = logger;
_callback = OnWaveMessage;
}
public void Start()
{
NativeWaveIn.WAVEFORMATEX pwfx = new NativeWaveIn.WAVEFORMATEX
{
wFormatTag = 1,
nChannels = 1,
nSamplesPerSec = (uint)_sampleRate,
wBitsPerSample = 16,
nBlockAlign = 2,
nAvgBytesPerSec = (uint)(_sampleRate * 2),
cbSize = 0
};
int num = NativeWaveIn.waveInOpen(out _handle, _deviceId, ref pwfx, _callback, IntPtr.Zero, 196608u);
if (num != 0)
{
throw new InvalidOperationException("waveInOpen failed with WinMM code " + num + ".");
}
_capturing = true;
for (int i = 0; i < 3; i++)
{
IntPtr lpData = Marshal.AllocHGlobal(4096);
IntPtr intPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeWaveIn.WAVEHDR)));
Marshal.StructureToPtr(new NativeWaveIn.WAVEHDR
{
lpData = lpData,
dwBufferLength = 4096u,
dwBytesRecorded = 0u,
dwUser = UIntPtr.Zero,
dwFlags = 0u,
dwLoops = 0u,
lpNext = IntPtr.Zero,
reserved = UIntPtr.Zero
}, intPtr, fDeleteOld: false);
_headers.Add(intPtr);
Check(NativeWaveIn.waveInPrepareHeader(_handle, intPtr, (uint)Marshal.SizeOf(typeof(NativeWaveIn.WAVEHDR))), "waveInPrepareHeader");
Check(NativeWaveIn.waveInAddBuffer(_handle, intPtr, (uint)Marshal.SizeOf(typeof(NativeWaveIn.WAVEHDR))), "waveInAddBuffer");
}
Check(NativeWaveIn.waveInStart(_handle), "waveInStart");
}
private void OnWaveMessage(IntPtr handle, uint message, IntPtr instance, IntPtr param1, IntPtr param2)
{
if (message != 960 || param1 == IntPtr.Zero)
{
return;
}
try
{
NativeWaveIn.WAVEHDR structure = (NativeWaveIn.WAVEHDR)Marshal.PtrToStructure(param1, typeof(NativeWaveIn.WAVEHDR));
if (structure.dwBytesRecorded != 0 && structure.lpData != IntPtr.Zero)
{
byte[] array = new byte[structure.dwBytesRecorded];
Marshal.Copy(structure.lpData, array, 0, array.Length);
lock (_sync)
{
if (_capturing)
{
_data.AddRange(array);
}
}
}
lock (_sync)
{
if (_capturing && !_closed)
{
structure.dwBytesRecorded = 0u;
Marshal.StructureToPtr(structure, param1, fDeleteOld: false);
NativeWaveIn.waveInAddBuffer(handle, param1, (uint)Marshal.SizeOf(typeof(NativeWaveIn.WAVEHDR)));
}
}
}
catch (Exception ex)
{
_log.Warning("Microphone callback error: " + ex.Message);
}
}
public byte[] StopAndGetPcm()
{
lock (_sync)
{
_capturing = false;
}
if (_handle != IntPtr.Zero)
{
NativeWaveIn.waveInStop(_handle);
NativeWaveIn.waveInReset(_handle);
}
lock (_sync)
{
return _data.ToArray();
}
}
private static void Check(int code, string operation)
{
if (code != 0)
{
throw new InvalidOperationException(operation + " failed with WinMM code " + code + ".");
}
}
public void Dispose()
{
if (_closed)
{
return;
}
_closed = true;
lock (_sync)
{
_capturing = false;
}
if (_handle != IntPtr.Zero)
{
NativeWaveIn.waveInStop(_handle);
NativeWaveIn.waveInReset(_handle);
foreach (IntPtr header in _headers)
{
try
{
NativeWaveIn.waveInUnprepareHeader(_handle, header, (uint)Marshal.SizeOf(typeof(NativeWaveIn.WAVEHDR)));
}
catch
{
}
try
{
NativeWaveIn.WAVEHDR wAVEHDR = (NativeWaveIn.WAVEHDR)Marshal.PtrToStructure(header, typeof(NativeWaveIn.WAVEHDR));
if (wAVEHDR.lpData != IntPtr.Zero)
{
Marshal.FreeHGlobal(wAVEHDR.lpData);
}
}
catch
{
}
Marshal.FreeHGlobal(header);
}
NativeWaveIn.waveInClose(_handle);
_handle = IntPtr.Zero;
}
_headers.Clear();
}
}
private static class NativeWaveIn
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct WAVEINCAPS
{
public ushort wMid;
public ushort wPid;
public uint vDriverVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string szPname;
public uint dwFormats;
public ushort wChannels;
public ushort wReserved1;
public uint dwSupport;
}
internal struct WAVEFORMATEX
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
}
internal struct WAVEHDR
{
public IntPtr lpData;
public uint dwBufferLength;
public uint dwBytesRecorded;
public UIntPtr dwUser;
public uint dwFlags;
public uint dwLoops;
public IntPtr lpNext;
public UIntPtr reserved;
}
internal delegate void WaveInProc(IntPtr hwi, uint uMsg, IntPtr dwInstance, IntPtr dwParam1, IntPtr dwParam2);
[DllImport("winmm.dll")]
internal static extern uint waveInGetNumDevs();
[DllImport("winmm.dll", CharSet = CharSet.Unicode, EntryPoint = "waveInGetDevCapsW")]
internal static extern int waveInGetDevCaps(UIntPtr uDeviceID, ref WAVEINCAPS pwic, uint cbwic);
[DllImport("winmm.dll")]
internal static extern int waveInOpen(out IntPtr phwi, uint uDeviceID, ref WAVEFORMATEX pwfx, WaveInProc dwCallback, IntPtr dwInstance, uint fdwOpen);
[DllImport("winmm.dll")]
internal static extern int waveInPrepareHeader(IntPtr hwi, IntPtr pwh, uint cbwh);
[DllImport("winmm.dll")]
internal static extern int waveInUnprepareHeader(IntPtr hwi, IntPtr pwh, uint cbwh);
[DllImport("winmm.dll")]
internal static extern int waveInAddBuffer(IntPtr hwi, IntPtr pwh, uint cbwh);
[DllImport("winmm.dll")]
internal static extern int waveInStart(IntPtr hwi);
[DllImport("winmm.dll")]
internal static extern int waveInStop(IntPtr hwi);
[DllImport("winmm.dll")]
internal static extern int waveInReset(IntPtr hwi);
[DllImport("winmm.dll")]
internal static extern int waveInClose(IntPtr hwi);
}
private readonly Instance _log;
private readonly AudioClip[] _clips = (AudioClip[])(object)new AudioClip[12];
private readonly List<MicDevice> _devices = new List<MicDevice>();
private int _clipCount;
private int _nextClip;
private int _deviceIndex;
private WinMicCapture _capture;
private AudioSource _source;
private GameObject _audioObject;
private bool _recordingEnabled;
private bool _recordingInProgress;
private bool _autonomous;
private bool _glitches;
private bool _disposed;
private float _recordStartedAt;
private float _nextAutonomousAt;
private float _nextScanAt;
private const int SampleRate = 22050;
private const int ClipSeconds = 5;
public int MicrophoneCount => _devices.Count;
public int SelectedMicrophoneIndex => _deviceIndex;
public string SelectedMicrophoneName
{
get
{
if (_devices.Count > 0 && _deviceIndex >= 0 && _deviceIndex < _devices.Count)
{
return _devices[_deviceIndex].Name;
}
return "No microphone detected";
}
}
public bool HasRecordedClip => _clipCount > 0;
public MimicAudio(Instance logger)
{
_log = logger;
RefreshDevices();
}
public void Update()
{
if (_disposed)
{
return;
}
if (Time.unscaledTime >= _nextScanAt)
{
_nextScanAt = Time.unscaledTime + 5f;
if (_devices.Count == 0 && !_recordingInProgress)
{
RefreshDevices();
}
}
if (_recordingInProgress && Time.unscaledTime - _recordStartedAt >= 5f)
{
FinishRecording();
}
if (_autonomous && _clipCount > 0 && Time.unscaledTime >= _nextAutonomousAt && ((Object)(object)_source == (Object)null || !_source.isPlaying))
{
PlayRandomClip();
_nextAutonomousAt = Time.unscaledTime + Random.Range(8f, 22f);
}
}
public void SetRecordingEnabled(bool enabled)
{
_recordingEnabled = enabled;
if (!enabled && _recordingInProgress)
{
FinishRecording();
}
_log.Msg("Native Windows microphone recording " + (enabled ? "enabled." : "disabled."));
if (enabled && _devices.Count == 0)
{
RefreshDevices();
}
}
public void SetAutonomous(bool enabled)
{
_autonomous = enabled;
_nextAutonomousAt = Time.unscaledTime + Random.Range(4f, 10f);
_log.Msg("Autonomous voice playback " + (enabled ? "enabled." : "disabled."));
}
public void SetGlitches(bool enabled)
{
_glitches = enabled;
_log.Msg("Audio glitches " + (enabled ? "enabled." : "disabled."));
}
public void CycleMicrophone()
{
if (_recordingInProgress)
{
_log.Warning("Finish the current recording before changing microphones.");
return;
}
RefreshDevices();
if (_devices.Count == 0)
{
_log.Warning("Windows waveIn did not report any recording devices.");
return;
}
_deviceIndex = (_deviceIndex + 1) % _devices.Count;
_log.Msg("Selected Windows microphone: " + _devices[_deviceIndex].Name + " (device " + _devices[_deviceIndex].Id + ").");
}
public void RefreshMicrophones()
{
RefreshDevices();
}
public void LogMicrophones()
{
RefreshDevices();
if (_devices.Count == 0)
{
_log.Warning("No Windows microphones found.");
return;
}
_log.Msg("=== Mimic Mod: Available Microphones ===");
for (int i = 0; i < _devices.Count; i++)
{
string text = ((i == _deviceIndex) ? " [SELECTED]" : "");
_log.Msg("[" + i + "] " + _devices[i].Name + text);
}
}
public string[] GetMicrophoneNames()
{
string[] array = new string[_devices.Count];
for (int i = 0; i < _devices.Count; i++)
{
array[i] = _devices[i].Name;
}
return array;
}
public void SelectMicrophone(int index)
{
if (_recordingInProgress)
{
_log.Warning("Finish the current recording before changing microphones.");
return;
}
if (index < 0 || index >= _devices.Count)
{
_log.Warning("Microphone index is out of range. Refresh the device list.");
return;
}
_deviceIndex = index;
_log.Msg("Selected Windows microphone: " + _devices[_deviceIndex].Name + " (device " + _devices[_deviceIndex].Id + ").");
}
public void SelectMicrophone(string name)
{
if (_recordingInProgress)
{
_log.Warning("Finish the current recording before changing microphones.");
return;
}
for (int i = 0; i < _devices.Count; i++)
{
if (string.Equals(_devices[i].Name, name, StringComparison.OrdinalIgnoreCase))
{
SelectMicrophone(i);
return;
}
}
_log.Warning("That microphone is no longer available. Refresh the microphone list.");
}
private void RefreshDevices()
{
try
{
string text = ((_devices.Count > 0 && _deviceIndex >= 0 && _deviceIndex < _devices.Count) ? _devices[_deviceIndex].Name : null);
_devices.Clear();
uint num = NativeWaveIn.waveInGetNumDevs();
for (uint num2 = 0u; num2 < num; num2++)
{
NativeWaveIn.WAVEINCAPS pwic = default(NativeWaveIn.WAVEINCAPS);
if (NativeWaveIn.waveInGetDevCaps(new UIntPtr(num2), ref pwic, (uint)Marshal.SizeOf(typeof(NativeWaveIn.WAVEINCAPS))) == 0)
{
_devices.Add(new MicDevice
{
Id = num2,
Name = (string.IsNullOrEmpty(pwic.szPname) ? ("Windows microphone " + num2) : pwic.szPname)
});
}
}
_deviceIndex = 0;
if (!string.IsNullOrEmpty(text))
{
for (int i = 0; i < _devices.Count; i++)
{
if (_devices[i].Name == text)
{
_deviceIndex = i;
break;
}
}
}
if (_devices.Count == 0)
{
_log.Warning("No Windows waveIn recording devices found. Check Windows sound settings and Quest Link microphone routing.");
}
else
{
_log.Msg("Windows microphones found: " + _devices.Count + ". Selected: " + _devices[_deviceIndex].Name);
}
}
catch (Exception ex)
{
_log.Error("Windows microphone enumeration failed: " + ex.Message);
}
}
public void RecordTestClip()
{
if (_disposed)
{
return;
}
if (!_recordingEnabled)
{
_log.Warning("Turn on 'Microphone Recording' in BoneMenu first.");
return;
}
if (_recordingInProgress)
{
_log.Warning("A recording is already in progress.");
return;
}
if (_devices.Count == 0)
{
RefreshDevices();
}
if (_devices.Count == 0)
{
_log.Warning("No Windows microphone available.");
return;
}
try
{
int index = Math.Max(0, Math.Min(_deviceIndex, _devices.Count - 1));
_capture = new WinMicCapture(_devices[index].Id, 22050, _log);
_capture.Start();
_recordingInProgress = true;
_recordStartedAt = Time.unscaledTime;
_log.Msg("Recording a 5-second native Windows microphone test. Speak now.");
}
catch (Exception ex)
{
_recordingInProgress = false;
if (_capture != null)
{
_capture.Dispose();
}
_capture = null;
_log.Error("Native microphone capture failed: " + ex.Message);
}
}
private void FinishRecording()
{
if (!_recordingInProgress)
{
return;
}
byte[] array = null;
try
{
if (_capture != null)
{
array = _capture.StopAndGetPcm();
}
if (array == null || array.Length < 2048)
{
_log.Warning("Microphone capture returned too little audio. Check the selected Windows input device and its privacy permission.");
return;
}
int num = array.Length / 2;
float[] array2 = new float[num];
int num2 = 0;
int num3 = 0;
while (num2 < num)
{
short num4 = (short)(array[num3] | (array[num3 + 1] << 8));
array2[num2] = (float)num4 / 32768f;
num2++;
num3 += 2;
}
AudioClip val = AudioClip.Create("MimicMic_" + DateTime.Now.Ticks, num, 1, 22050, false);
val.SetData(array2, 0);
AudioClip val2 = _clips[_nextClip];
if ((Object)(object)val2 != (Object)null)
{
Object.Destroy((Object)(object)val2);
}
_clips[_nextClip] = val;
_nextClip = (_nextClip + 1) % _clips.Length;
_clipCount = Math.Min(_clipCount + 1, _clips.Length);
_log.Msg("Native mic test captured " + num + " samples. Stored clips: " + _clipCount + ". Use Play Latest Clip.");
}
catch (Exception ex)
{
_log.Error("Could not finish native microphone recording: " + ex.Message);
}
finally
{
_recordingInProgress = false;
if (_capture != null)
{
_capture.Dispose();
}
_capture = null;
}
}
public void PlayLatest()
{
AudioClip val = ((_clipCount > 0) ? _clips[(_nextClip - 1 + _clips.Length) % _clips.Length] : null);
if ((Object)(object)val == (Object)null)
{
_log.Warning("No recorded clips yet. Enable recording and record a test clip first.");
}
else
{
PlayClip(val, null);
}
}
public void PlayLatestAt(Transform location)
{
AudioClip val = ((_clipCount > 0) ? _clips[(_nextClip - 1 + _clips.Length) % _clips.Length] : null);
if (!((Object)(object)val == (Object)null))
{
PlayClip(val, location);
}
}
private void PlayRandomClip()
{
for (int i = 0; i < _clips.Length; i++)
{
AudioClip val = _clips[Random.Range(0, _clips.Length)];
if ((Object)(object)val != (Object)null)
{
PlayClip(val, null);
break;
}
}
}
private void PlayClip(AudioClip clip, Transform location)
{
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)clip == (Object)null || _disposed)
{
return;
}
EnsureAudioSource();
if ((Object)(object)_source == (Object)null)
{
return;
}
_source.Stop();
((Component)_source).transform.SetParent((Transform)null, false);
if ((Object)(object)location != (Object)null)
{
((Component)_source).transform.position = location.position;
_source.spatialBlend = 1f;
_source.minDistance = 1f;
_source.maxDistance = 22f;
_source.rolloffMode = (AudioRolloffMode)0;
}
else
{
Transform val = (((Object)(object)Camera.main != (Object)null) ? ((Component)Camera.main).transform : null);
if ((Object)(object)val != (Object)null)
{
((Component)_source).transform.SetParent(val, false);
}
((Component)_source).transform.localPosition = new Vector3(0f, 0f, 1.2f);
_source.spatialBlend = 0f;
}
_source.clip = clip;
_source.pitch = (_glitches ? Random.Range(0.78f, 1.2f) : 1f);
_source.volume = (_glitches ? Random.Range(0.55f, 1f) : 1f);
_source.Play();
_log.Msg("Playing captured microphone audio" + (((Object)(object)location != (Object)null) ? " from Mimic position" : " near the player") + (_glitches ? " with glitch variation." : "."));
}
private void EnsureAudioSource()
{
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Expected O, but got Unknown
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)_source != (Object)null) && !_disposed)
{
_audioObject = new GameObject("MimicMod_Audio");
Object.DontDestroyOnLoad((Object)(object)_audioObject);
Transform val = (((Object)(object)Camera.main != (Object)null) ? ((Component)Camera.main).transform : null);
if ((Object)(object)val != (Object)null)
{
_audioObject.transform.SetParent(val, false);
}
_audioObject.transform.localPosition = new Vector3(0f, 0f, 1.2f);
_source = _audioObject.AddComponent<AudioSource>();
_source.playOnAwake = false;
_source.loop = false;
_source.spatialBlend = 0f;
_source.dopplerLevel = 0f;
}
}
public void StopPlayback()
{
if ((Object)(object)_source != (Object)null)
{
_source.Stop();
}
_log.Msg("Mimic audio playback stopped.");
}
public void Dispose()
{
if (_disposed)
{
return;
}
_autonomous = false;
if (_recordingInProgress)
{
FinishRecording();
}
if (_capture != null)
{
_capture.Dispose();
_capture = null;
}
_disposed = true;
if ((Object)(object)_source != (Object)null)
{
_source.Stop();
}
for (int i = 0; i < _clips.Length; i++)
{
if ((Object)(object)_clips[i] != (Object)null)
{
Object.Destroy((Object)(object)_clips[i]);
}
}
if ((Object)(object)_audioObject != (Object)null)
{
Object.Destroy((Object)(object)_audioObject);
}
}
}
internal enum MimicAction
{
Observe,
Stalk,
TakeCover,
Investigate,
Rush,
Attack,
Jumpscare,
Ignore
}
internal sealed class MimicBehavior
{
private float _nextDecisionAt;
private float _lastJumpscareAt = -99999f;
private float _lastAttackAt = -99999f;
public MimicAction Decide(MimicSettings settings, MimicLearning learning, float now, float distanceToPlayer, bool playerLookingAtMimic, bool lineOfSight, bool playerMoving, bool canAttack, bool jumpscareAvailable, MimicEncounters encounters = null)
{
if (settings == null || !settings.enabled)
{
return MimicAction.Observe;
}
if (settings.behaviorMode == MimicBehaviorMode.NonHostile)
{
if (!lineOfSight)
{
return MimicAction.Investigate;
}
return MimicAction.Observe;
}
float num = settings.rushChance;
if (encounters != null && encounters.totalEncounters >= 3)
{
float num2 = (float)encounters.totalRushes / (float)Math.Max(1, encounters.totalEncounters);
float num3 = (float)encounters.totalAttacks / (float)Math.Max(1, encounters.totalEncounters);
float num4 = Mathf.Clamp(0.85f + num2 * 0.35f + num3 * 0.15f, 0.75f, 1.5f);
num = Mathf.Clamp01(num * num4);
}
if (settings.behaviorMode == MimicBehaviorMode.Jumpscare && jumpscareAvailable && now - _lastJumpscareAt >= settings.jumpscareCooldown && distanceToPlayer < 5f && lineOfSight)
{
_lastJumpscareAt = now;
return MimicAction.Jumpscare;
}
if (settings.behaviorMode == MimicBehaviorMode.RuthlessAttacker)
{
if (settings.attacksEnabled && canAttack && now - _lastAttackAt >= settings.attackCooldown)
{
_lastAttackAt = now;
return MimicAction.Attack;
}
if (distanceToPlayer > 2.2f)
{
return MimicAction.Rush;
}
if (!settings.attacksEnabled)
{
return MimicAction.Observe;
}
return MimicAction.Attack;
}
if (now < _nextDecisionAt)
{
return MimicAction.Observe;
}
_nextDecisionAt = now + Random.Range(0.65f, 1.8f);
if (settings.attacksEnabled && canAttack && distanceToPlayer < 2.15f && now - _lastAttackAt >= settings.attackCooldown && Random.value < 0.25f)
{
_lastAttackAt = now;
return MimicAction.Attack;
}
if (playerLookingAtMimic && distanceToPlayer > 3f)
{
return MimicAction.TakeCover;
}
if (lineOfSight && distanceToPlayer > 3f && learning != null && learning.AverageTurn() > 35f && Random.value < 0.2f)
{
return MimicAction.TakeCover;
}
if (!lineOfSight)
{
return MimicAction.Investigate;
}
if (distanceToPlayer > settings.stalkDistance)
{
if (!(Random.value < num))
{
return MimicAction.Stalk;
}
return MimicAction.Rush;
}
if (learning != null && learning.AveragePause() > 2.5f && Random.value < num * 0.5f)
{
return MimicAction.Rush;
}
if (!playerMoving)
{
if (!(Random.value < 0.35f))
{
return MimicAction.Observe;
}
return MimicAction.TakeCover;
}
return MimicAction.Stalk;
}
}
internal sealed class MimicDataManager
{
private readonly Instance _log;
private readonly JsonSerializerOptions _options = new JsonSerializerOptions
{
WriteIndented = true,
IncludeFields = true
};
public MimicSettings Settings { get; private set; }
public MimicLearning Learning { get; private set; }
public MimicEncounters Encounters { get; private set; }
public string DirectoryPath { get; private set; }
public MimicDataManager(Instance logger, string rootDirectory = null)
{
_log = logger;
DirectoryPath = Path.Combine(string.IsNullOrWhiteSpace(rootDirectory) ? Environment.CurrentDirectory : rootDirectory, "Userdata", "Mimic");
Settings = new MimicSettings();
Learning = new MimicLearning();
Encounters = new MimicEncounters();
}
public void Load()
{
Directory.CreateDirectory(DirectoryPath);
Settings = LoadFile("MimicSettings.json", Settings);
Learning = LoadFile("MimicLearning.json", Learning);
Encounters = LoadFile("MimicEncounters.json", Encounters);
if (Settings == null)
{
Settings = new MimicSettings();
}
if (Learning == null)
{
Learning = new MimicLearning();
}
if (Encounters == null)
{
Encounters = new MimicEncounters();
}
Settings.ClampValues();
SaveAll();
_log.Msg("Mimic data directory: " + DirectoryPath);
}
public void SaveAll()
{
SaveFile("MimicSettings.json", Settings);
SaveFile("MimicLearning.json", Learning);
SaveFile("MimicEncounters.json", Encounters);
SaveBrain(JsonSerializer.Serialize(new
{
version = 1,
behaviorMode = Settings.behaviorMode.ToString(),
enabled = Settings.enabled,
learningEnabled = Settings.learningEnabled,
sessions = Learning.sessions,
averageSpeed = Learning.AverageSpeed(),
averagePause = Learning.AveragePause(),
averageTurn = Learning.AverageTurn(),
savedUtc = DateTime.UtcNow.ToString("o")
}, _options));
}
public void SaveBrain(string json)
{
try
{
WriteAtomically(Path.Combine(DirectoryPath, "MimicBrain.json"), json ?? "{}");
}
catch (Exception ex)
{
_log.Error("Could not save MimicBrain.json: " + ex.Message);
}
}
private T LoadFile<T>(string name, T fallback) where T : class
{
string path = Path.Combine(DirectoryPath, name);
if (!File.Exists(path))
{
return fallback;
}
try
{
return JsonSerializer.Deserialize<T>(File.ReadAllText(path), _options) ?? fallback;
}
catch (Exception ex)
{
_log.Warning("Could not load " + name + ": " + ex.Message + ". Keeping defaults.");
return fallback;
}
}
private void SaveFile<T>(string name, T value)
{
try
{
string content = JsonSerializer.Serialize(value, _options);
WriteAtomically(Path.Combine(DirectoryPath, name), content);
}
catch (Exception ex)
{
_log.Error("Could not save " + name + ": " + ex.Message);
}
}
private static void WriteAtomically(string path, string content)
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
string text = path + ".tmp";
File.WriteAllText(text, content);
if (File.Exists(path))
{
string destinationBackupFileName = path + ".bak";
try
{
File.Replace(text, path, destinationBackupFileName, ignoreMetadataErrors: true);
return;
}
catch
{
File.Copy(text, path, overwrite: true);
File.Delete(text);
return;
}
}
File.Move(text, path);
}
}
[Serializable]
public sealed class MimicEncounterRecord
{
public string utc;
public string eventType;
public string note;
}
[Serializable]
public sealed class MimicEncounters
{
public int version = 1;
public int totalEncounters;
public int totalRushes;
public int totalAttacks;
public int totalJumpscares;
public string lastEncounterUtc = "";
public List<MimicEncounterRecord> history = new List<MimicEncounterRecord>();
public void Record(string eventType, string note = "")
{
string text = (eventType ?? "event").Trim().ToLowerInvariant();
string utc = (lastEncounterUtc = DateTime.UtcNow.ToString("o"));
if (text.Contains("encounter"))
{
totalEncounters++;
}
if (text.Contains("rush"))
{
totalRushes++;
}
if (text.Contains("attack"))
{
totalAttacks++;
}
if (text.Contains("jumpscare"))
{
totalJumpscares++;
}
history.Add(new MimicEncounterRecord
{
utc = utc,
eventType = text,
note = (note ?? "")
});
if (history.Count > 250)
{
history.RemoveAt(0);
}
}
}
[Serializable]
public sealed class MimicLearning
{
public int version = 1;
public int sessions;
public List<float> movementGaps = new List<float>();
public List<float> walkingBursts = new List<float>();
public List<float> pauses = new List<float>();
public List<float> turns = new List<float>();
public List<float> directionChanges = new List<float>();
public List<string> observations = new List<string>();
[NonSerialized]
private Vector3 _lastPosition;
[NonSerialized]
private Vector3 _lastForward;
[NonSerialized]
private float _lastSampleAt;
[NonSerialized]
private float _lastMovingAt;
[NonSerialized]
private bool _initialized;
[NonSerialized]
private bool _wasMoving;
public void BeginSession()
{
sessions++;
_initialized = false;
}
public void Observe(Vector3 position, Vector3 forward, float now)
{
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0117: Unknown result type (might be due to invalid IL or missing references)
//IL_011c: Unknown result type (might be due to invalid IL or missing references)
//IL_0139: Unknown result type (might be due to invalid IL or missing references)
//IL_013b: Unknown result type (might be due to invalid IL or missing references)
//IL_0140: Unknown result type (might be due to invalid IL or missing references)
//IL_0145: Unknown result type (might be due to invalid IL or missing references)
//IL_015b: Unknown result type (might be due to invalid IL or missing references)
//IL_0160: Unknown result type (might be due to invalid IL or missing references)
//IL_018d: Unknown result type (might be due to invalid IL or missing references)
//IL_018e: Unknown result type (might be due to invalid IL or missing references)
//IL_0194: Unknown result type (might be due to invalid IL or missing references)
//IL_0195: Unknown result type (might be due to invalid IL or missing references)
if (!_initialized)
{
_lastPosition = position;
_lastForward = forward;
_lastSampleAt = now;
_lastMovingAt = now;
_initialized = true;
return;
}
float num = Mathf.Clamp(now - _lastSampleAt, 0f, 5f);
if (!(num <= 0.001f))
{
float num2 = Vector3.Distance(position, _lastPosition) / num;
bool flag = num2 > 0.12f;
Add(movementGaps, num, 80);
if (flag)
{
Add(walkingBursts, num2, 80);
}
if (_wasMoving && !flag)
{
Add(pauses, Mathf.Max(0f, now - _lastMovingAt), 80);
Add(observations, "pause:" + (now - _lastMovingAt).ToString("F2"), 150);
}
if (flag && !_wasMoving)
{
_lastMovingAt = now;
Add(observations, "move-start:" + now.ToString("F2"), 150);
}
float num3 = Vector3.Angle(_lastForward, forward);
if (num3 >= 12f)
{
Add(turns, num3, 80);
}
Vector3 val = position - _lastPosition;
if (((Vector3)(ref val)).sqrMagnitude > 0.02f && flag && Vector3.Dot(((Vector3)(ref val)).normalized, forward) < -0.1f)
{
Add(directionChanges, num3, 80);
}
if (flag)
{
_lastMovingAt = now;
}
_wasMoving = flag;
_lastPosition = position;
_lastForward = forward;
_lastSampleAt = now;
}
}
public float AverageSpeed()
{
return Average(walkingBursts);
}
public float AveragePause()
{
return Average(pauses);
}
public float AverageTurn()
{
return Average(turns);
}
private static void Add(List<float> list, float value, int max)
{
list.Add(value);
if (list.Count > max)
{
list.RemoveAt(0);
}
}
private static void Add(List<string> list, string value, int max)
{
list.Add(value);
if (list.Count > max)
{
list.RemoveAt(0);
}
}
private static float Average(List<float> list)
{
if (list == null || list.Count == 0)
{
return 0f;
}
float num = 0f;
foreach (float item in list)
{
num += item;
}
return num / (float)list.Count;
}
}
public enum MimicBehaviorMode
{
AdaptiveStalker,
Jumpscare,
RuthlessAttacker,
NonHostile
}
[Serializable]
public sealed class MimicSettings
{
public int version = 1;
public MimicBehaviorMode behaviorMode;
public bool enabled;
public bool stalkingEnabled = true;
public bool attacksEnabled = true;
public bool visualGlitchesEnabled;
public bool learningEnabled = true;
public bool ttsEnabled;
public bool selfAwareEnabled;
public float ttsPitch = 0.75f;
public float ttsFrequency = 0.4f;
public float stalkDistance = 7f;
public float rushChance = 0.22f;
public float attackCooldown = 4f;
public float jumpscareCooldown = 30f;
public void ClampValues()
{
ttsPitch = Clamp(ttsPitch, 0.5f, 1.5f);
ttsFrequency = Clamp(ttsFrequency, 0f, 1f);
stalkDistance = Clamp(stalkDistance, 2f, 25f);
rushChance = Clamp(rushChance, 0f, 1f);
attackCooldown = Clamp(attackCooldown, 0.5f, 60f);
jumpscareCooldown = Clamp(jumpscareCooldown, 1f, 300f);
}
private static float Clamp(float v, float min, float max)
{
if (!(v < min))
{
if (!(v > max))
{
return v;
}
return max;
}
return min;
}
}
internal sealed class MimicTTS : IDisposable
{
private readonly Instance _log;
private object _voice;
public bool Enabled { get; private set; }
public float Pitch { get; private set; } = 0.75f;
public float Frequency { get; private set; } = 0.4f;
public MimicTTS(Instance logger)
{
_log = logger;
}
public void Configure(bool enabled, float pitch, float frequency)
{
Enabled = enabled;
Pitch = Clamp(pitch, 0.5f, 1.5f);
Frequency = Clamp(frequency, 0f, 1f);
}
public IReadOnlyList<string> GetInstalledVoices()
{
List<string> list = new List<string>();
if (!OperatingSystem.IsWindows())
{
_log.Warning("Windows SAPI voice enumeration is only available on Windows.");
return list;
}
object obj = null;
try
{
object voice = GetVoice();
obj = voice.GetType().InvokeMember("GetVoices", BindingFlags.InvokeMethod, null, voice, new object[2] { "", "" });
if (obj == null)
{
return list;
}
int num = Convert.ToInt32(obj.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, obj, null));
for (int i = 0; i < num; i++)
{
object obj2 = obj.GetType().InvokeMember("Item", BindingFlags.InvokeMethod, null, obj, new object[1] { i });
if (obj2 != null)
{
string text = Convert.ToString(obj2.GetType().InvokeMember("GetDescription", BindingFlags.InvokeMethod, null, obj2, new object[1] { 0 }));
if (!string.IsNullOrWhiteSpace(text))
{
list.Add(text);
}
}
}
}
catch (Exception ex)
{
_log.Warning("Could not enumerate Windows SAPI voices: " + ex.Message);
}
finally
{
ReleaseComObjectIfNeeded(obj);
}
return list;
}
public bool Speak(string text)
{
if (!Enabled || string.IsNullOrWhiteSpace(text))
{
return false;
}
if (!OperatingSystem.IsWindows())
{
_log.Warning("Windows SAPI speech is only available on Windows.");
return false;
}
try
{
object voice = GetVoice();
int num = (int)Math.Round((Pitch - 1f) * 20f);
string text2 = SecurityElement.Escape(text.Trim());
string text3 = "<pitch absmiddle=\"" + num + "\">" + text2 + "</pitch>";
voice.GetType().InvokeMember("Speak", BindingFlags.InvokeMethod, null, voice, new object[2] { text3, 8 });
return true;
}
catch (Exception ex)
{
_log.Error("Windows speech synthesis failed: " + ex.Message);
return false;
}
}
private object GetVoice()
{
if (_voice != null)
{
return _voice;
}
if (!OperatingSystem.IsWindows())
{
throw new PlatformNotSupportedException("Windows SAPI is only available on Windows.");
}
Type typeFromProgID = Type.GetTypeFromProgID("SAPI.SpVoice");
if (typeFromProgID == null)
{
throw new InvalidOperationException("Windows SAPI.SpVoice is unavailable.");
}
_voice = Activator.CreateInstance(typeFromProgID);
if (_voice == null)
{
throw new InvalidOperationException("Windows SAPI.SpVoice could not be created.");
}
return _voice;
}
public void Dispose()
{
object voice = _voice;
_voice = null;
if (voice == null)
{
return;
}
try
{
if (OperatingSystem.IsWindows())
{
voice.GetType().InvokeMember("Speak", BindingFlags.InvokeMethod, null, voice, new object[2] { "", 3 });
}
}
catch (Exception ex)
{
_log.Warning("Could not stop Windows speech during shutdown: " + ex.Message);
}
finally
{
ReleaseComObjectIfNeeded(voice);
}
}
private static void ReleaseComObjectIfNeeded(object value)
{
if (value == null || !OperatingSystem.IsWindows())
{
return;
}
try
{
if (Marshal.IsComObject(value))
{
Marshal.ReleaseComObject(value);
}
}
catch
{
}
}
private static float Clamp(float value, float min, float max)
{
if (float.IsNaN(value) || float.IsInfinity(value))
{
return min;
}
if (!(value < min))
{
if (!(value > max))
{
return value;
}
return max;
}
return min;
}
}
public sealed class Mod : MelonMod
{
private MimicAI _ai;
private MimicDataManager _data;
private MimicTTS _tts;
private float _nextSaveAt;
private bool _menuCreated;
public override void OnInitializeMelon()
{
_data = new MimicDataManager(((MelonBase)this).LoggerInstance);
_data.Load();
if (_data.Learning != null)
{
_data.Learning.BeginSession();
}
_tts = new MimicTTS(((MelonBase)this).LoggerInstance);
ConfigureTTS();
_ai = new MimicAI(((MelonBase)this).LoggerInstance);
ApplySettingsToAI();
((MelonBase)this).LoggerInstance.Msg("Mimic 0.5.0 initialized. Microphone features are not included.");
}
public override void OnLateInitializeMelon()
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
if (!_menuCreated && _data != null)
{
_menuCreated = true;
Page root = Page.Root.CreatePage("Mimic", Color.cyan, 0, true);
BuildMainPage(root);
BuildBehaviorPages(root);
BuildLearningPage(root);
BuildVoicePage(root);
BuildDiagnosticsPage(root);
((MelonBase)this).LoggerInstance.Msg("Mimic BoneMenu created with separate AI, behavior, learning, voice, and diagnostics categories. No microphone controls are present.");
}
}
private void BuildMainPage(Page root)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Unknown result type (might be due to invalid IL or missing references)
//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
//IL_00df: Unknown result type (might be due to invalid IL or missing references)
//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
//IL_0119: Unknown result type (might be due to invalid IL or missing references)
//IL_0136: Unknown result type (might be due to invalid IL or missing references)
//IL_0152: Unknown result type (might be due to invalid IL or missing references)
Page obj = root.CreatePage("AI Control", Color.cyan, 0, true);
obj.CreateBool("Mimic AI Enabled", Color.cyan, _data.Settings.enabled, (Action<bool>)delegate(bool value)
{
_data.Settings.enabled = value;
ApplySettingsToAI();
Save();
});
obj.CreateEnum("Behavior Mode", Color.cyan, (Enum)_data.Settings.behaviorMode, (Action<Enum>)delegate(Enum value)
{
_data.Settings.behaviorMode = (MimicBehaviorMode)(object)value;
ApplySettingsToAI();
Save();
});
obj.CreateFunction("Refresh Existing Doppel Bots", Color.white, (Action)delegate
{
_ai?.RefreshBots();
((MelonBase)this).LoggerInstance.Msg("Requested a refresh of existing Doppel bots.");
});
obj.CreateFunction("Reapply Settings", Color.white, (Action)delegate
{
ApplySettingsToAI();
Save();
((MelonBase)this).LoggerInstance.Msg("Mimic settings reapplied.");
});
obj.CreateFunction("Save All Data Now", Color.green, (Action)Save);
Page obj2 = obj.CreatePage("Quick Profiles", Color.cyan, 0, true);
obj2.CreateFunction("Balanced Stalker", Color.cyan, (Action)delegate
{
ApplyProfile(MimicBehaviorMode.AdaptiveStalker, stalking: true, attacks: true, glitches: false, learning: true, 7f, 0.22f, 4f, 30f);
});
obj2.CreateFunction("Patient / Less Aggressive", Color.cyan, (Action)delegate
{
ApplyProfile(MimicBehaviorMode.AdaptiveStalker, stalking: true, attacks: false, glitches: false, learning: true, 10f, 0.08f, 7f, 45f);
});
obj2.CreateFunction("Aggressive Pursuer", Color.red, (Action)delegate
{
ApplyProfile(MimicBehaviorMode.RuthlessAttacker, stalking: true, attacks: true, glitches: false, learning: true, 5f, 0.55f, 2f, 20f);
});
obj2.CreateFunction("Jumpscare Focus", Color.magenta, (Action)delegate
{
ApplyProfile(MimicBehaviorMode.Jumpscare, stalking: true, attacks: false, glitches: true, learning: true, 7f, 0.15f, 4f, 18f);
});
obj2.CreateFunction("Non-Hostile Observer", Color.white, (Action)delegate
{
ApplyProfile(MimicBehaviorMode.NonHostile, stalking: false, attacks: true, glitches: false, learning: true, 9f, 0f, 4f, 30f);
});
}
private void BuildBehaviorPages(Page root)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_0074: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Unknown result type (might be due to invalid IL or missing references)
//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
//IL_0126: Unknown result type (might be due to invalid IL or missing references)
//IL_0143: Unknown result type (might be due to invalid IL or missing references)
//IL_0160: Unknown result type (might be due to invalid IL or missing references)
//IL_017d: Unknown result type (might be due to invalid IL or missing references)
//IL_0199: Unknown result type (might be due to invalid IL or missing references)
//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
//IL_0220: Unknown result type (might be due to invalid IL or missing references)
Page obj = root.CreatePage("Behavior Tuning", Color.cyan, 0, true);
Page obj2 = obj.CreatePage("Stalking & Movement", Color.cyan, 0, true);
obj2.CreateBool("Stalking / Cover Enabled", Color.cyan, _data.Settings.stalkingEnabled, (Action<bool>)delegate(bool value)
{
_data.Settings.stalkingEnabled = value;
ApplySettingsToAI();
Save();
});
obj2.CreateFunction("Increase Stalk Distance (+1m)", Color.cyan, (Action)delegate
{
ChangeFloat("stalk distance", 1f, 2f, 25f, delegate(float value)
{
_data.Settings.stalkDistance = value;
}, () => _data.Settings.stalkDistance);
});
obj2.CreateFunction("Decrease Stalk Distance (-1m)", Color.cyan, (Action)delegate
{
ChangeFloat("stalk distance", -1f, 2f, 25f, delegate(float value)
{
_data.Settings.stalkDistance = value;
}, () => _data.Settings.stalkDistance);
});
obj2.CreateFunction("Increase Rush Chance (+5%)", Color.cyan, (Action)delegate
{
ChangeFloat("rush chance", 0.05f, 0f, 1f, delegate(float value)
{
_data.Settings.rushChance = value;
}, () => _data.Settings.rushChance, percent: true);
});
obj2.CreateFunction("Decrease Rush Chance (-5%)", Color.cyan, (Action)delegate
{
ChangeFloat("rush chance", -0.05f, 0f, 1f, delegate(float value)
{
_data.Settings.rushChance = value;
}, () => _data.Settings.rushChance, percent: true);
});
obj2.CreateFunction("Show Movement Tuning", Color.white, (Action)delegate
{
((MelonBase)this).LoggerInstance.Msg("Stalk distance=" + _data.Settings.stalkDistance.ToString("F1") + "m; rush chance=" + (_data.Settings.rushChance * 100f).ToString("F0") + "%.");
});
Page obj3 = obj.CreatePage("Combat & Cooldowns", Color.red, 0, true);
obj3.CreateBool("Close-Range Attacks Enabled", Color.red, _data.Settings.attacksEnabled, (Action<bool>)delegate(bool value)
{
_data.Settings.attacksEnabled = value;
ApplySettingsToAI();
Save();
});
obj3.CreateFunction("Faster Attacks (-0.5s cooldown)", Color.red, (Action)delegate
{
ChangeFloat("attack cooldown", -0.5f, 0.5f, 60f, delegate(float value)
{
_data.Settings.attackCooldown = value;
}, () => _data.Settings.attackCooldown);
});
obj3.CreateFunction("Slower Attacks (+0.5s cooldown)", Color.red, (Action)delegate
{
ChangeFloat("attack cooldown", 0.5f, 0.5f, 60f, delegate(float value)
{
_data.Settings.attackCooldown = value;
}, () => _data.Settings.attackCooldown);
});
obj3.CreateFunction("Shorter Jumpscare Cooldown (-5s)", Color.magenta, (Action)delegate
{
ChangeFloat("jumpscare cooldown", -5f, 1f, 300f, delegate(float value)
{
_data.Settings.jumpscareCooldown = value;
}, () => _data.Settings.jumpscareCooldown);
});
obj3.CreateFunction("Longer Jumpscare Cooldown (+5s)", Color.magenta, (Action)delegate
{
ChangeFloat("jumpscare cooldown", 5f, 1f, 300f, delegate(float value)
{
_data.Settings.jumpscareCooldown = value;
}, () => _data.Settings.jumpscareCooldown);
});
obj3.CreateFunction("Show Combat Tuning", Color.white, (Action)delegate
{
((MelonBase)this).LoggerInstance.Msg("Attacks=" + _data.Settings.attacksEnabled + "; attack cooldown=" + _data.Settings.attackCooldown.ToString("F1") + "s; jumpscare cooldown=" + _data.Settings.jumpscareCooldown.ToString("F1") + "s.");
});
Page obj4 = obj.CreatePage("Experimental", Color.magenta, 0, true);
obj4.CreateBool("Self-Aware Mode", Color.magenta, _data.Settings.selfAwareEnabled, (Action<bool>)delegate(bool value)
{
_data.Settings.selfAwareEnabled = value;
ApplySettingsToAI();
Save();
});
obj4.CreateBool("Visual Glitches", Color.magenta, _data.Settings.visualGlitchesEnabled, (Action<bool>)delegate(bool value)
{
_data.Settings.visualGlitchesEnabled = value;
ApplySettingsToAI();
Save();
});
obj4.CreateFunction("Reset Experimental Toggles", Color.white, (Action)delegate
{
_data.Settings.selfAwareEnabled = false;
_data.Settings.visualGlitchesEnabled = false;
ApplySettingsToAI();
Save();
((MelonBase)this).LoggerInstance.Msg("Self-aware mode and visual glitches disabled.");
});
}
private void BuildLearningPage(Page root)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
//IL_009b: Unknown result type (might be due to invalid IL or missing references)
Page obj = root.CreatePage("Learning & Memory", Color.cyan, 0, true);
obj.CreateBool("Player Learning Enabled", Color.cyan, _data.Settings.learningEnabled, (Action<bool>)delegate(bool value)
{
_data.Settings.learningEnabled = value;
ApplySettingsToAI();
Save();
});
obj.CreateFunction("Show Learning Statistics", Color.white, (Action)PrintLearningStats);
obj.CreateFunction("Save Learning & Settings", Color.green, (Action)Save);
obj.CreateFunction("Forget Learned Movement History", Color.red, (Action)delegate
{
_data.Learning.movementGaps.Clear();
_data.Learning.walkingBursts.Clear();
_data.Learning.pauses.Clear();
_data.Learning.turns.Clear();
_data.Learning.directionChanges.Clear();
_data.Learning.observations.Clear();
_data.Learning.sessions = 0;
Save();
((MelonBase)this).LoggerInstance.Msg("Mimic movement history cleared. Encounter history was preserved.");
});
obj.CreateFunction("Forget Encounter History", Color.red, (Action)delegate
{
_data.Encounters.history.Clear();
_data.Encounters.totalEncounters = 0;
_data.Encounters.totalRushes = 0;
_data.Encounters.totalAttacks = 0;
_data.Encounters.totalJumpscares = 0;
_data.Encounters.lastEncounterUtc = string.Empty;
Save();
((MelonBase)this).LoggerInstance.Msg("Mimic encounter history cleared. Movement learning was preserved.");
});
}
private void BuildVoicePage(Page root)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
Page obj = root.CreatePage("Voice & TTS", Color.cyan, 0, true);
obj.CreateBool("Windows TTS Enabled", Color.cyan, _data.Settings.ttsEnabled, (Action<bool>)delegate(bool value)
{
_data.Settings.ttsEnabled = value;
ConfigureTTS();
Save();
});
obj.CreateFunction("List Installed Windows Speech Voices", Color.white, (Action)ListTTSVoices);
obj.CreateFunction("Lower Voice Pitch", Color.magenta, (Action)delegate
{
ChangeFloat("TTS pitch", -0.05f, 0.5f, 1.5f, delegate(float value)
{
_data.Settings.ttsPitch = value;
}, () => _data.Settings.ttsPitch);
});
obj.CreateFunction("Raise Voice Pitch", Color.magenta, (Action)delegate
{
ChangeFloat("TTS pitch", 0.05f, 0.5f, 1.5f, delegate(float value)
{
_data.Settings.ttsPitch = value;
}, () => _data.Settings.ttsPitch);
});
obj.CreateFunction("Lower Voice Frequency", Color.magenta, (Action)delegate
{
ChangeFloat("TTS frequency", -0.05f, 0f, 1f, delegate(float value)
{
_data.Settings.ttsFrequency = value;
}, () => _data.Settings.ttsFrequency);
});
obj.CreateFunction("Raise Voice Frequency", Color.magenta, (Action)delegate
{
ChangeFloat("TTS frequency", 0.05f, 0f, 1f, delegate(float value)
{
_data.Settings.ttsFrequency = value;
}, () => _data.Settings.ttsFrequency);
});
obj.CreateFunction("Test Windows TTS", Color.green, (Action)delegate
{
if (_tts == null || !_data.Settings.ttsEnabled || !_tts.Speak("Mimic voice test."))
{
((MelonBase)this).LoggerInstance.Warning("TTS test failed. Enable TTS and check Windows SAPI and the default audio output.");
}
});
obj.CreateFunction("TTS Routing Note", Color.white, (Action)delegate
{
((MelonBase)this).LoggerInstance.Msg("This version uses Windows default audio output; speech is not yet spatially positioned at the bot.");
});
}
private void BuildDiagnosticsPage(Page root)
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_006f: Unknown result type (might be due to invalid IL or missing references)
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
Page obj = root.CreatePage("Diagnostics & Tools", Color.white, 0, true);
obj.CreateFunction("Refresh Existing Doppel Bots", Color.white, (Action)delegate
{
_ai?.RefreshBots();
});
obj.CreateFunction("Show Encounter Counts", Color.white, (Action)delegate
{
((MelonBase)this).LoggerInstance.Msg("Encounters=" + _data.Encounters.totalEncounters + ", rushes=" + _data.Encounters.totalRushes + ", attacks=" + _data.Encounters.totalAttacks + ", jumpscares=" + _data.Encounters.totalJumpscares);
});
obj.CreateFunction("Print Data Directory", Color.white, (Action)delegate
{
((MelonBase)this).LoggerInstance.Msg("Mimic data path: " + _data.DirectoryPath);
});
obj.CreateFunction("Print Current Settings", Color.white, (Action)PrintSettings);
obj.CreateFunction("Save All JSON Files", Color.green, (Action)Save);
obj.CreateFunction("Reapply Settings to AI", Color.cyan, (Action)ApplySettingsToAI);
}
private void ApplyProfile(MimicBehaviorMode mode, bool stalking, bool attacks, bool glitches, bool learning, float stalkDistance, float rushChance, float attackCooldown, float jumpscareCooldown)
{
MimicSettings settings = _data.Settings;
settings.behaviorMode = mode;
settings.enabled = true;
settings.stalkingEnabled = stalking;
settings.attacksEnabled = attacks;
settings.visualGlitchesEnabled = glitches;
settings.learningEnabled = learning;
settings.stalkDistance = stalkDistance;
settings.rushChance = rushChance;
settings.attackCooldown = attackCooldown;
settings.jumpscareCooldown = jumpscareCooldown;
ApplySettingsToAI();
Save();
((MelonBase)this).LoggerInstance.Msg("Applied Mimic profile: " + mode.ToString() + ". Review the Behavior Tuning pages to customize it.");
}
private void ChangeFloat(string label, float delta, float min, float max, Action<float> setter, Func<float> getter, bool percent = false)
{
if (_data != null)
{
float num = Mathf.Clamp(getter() + delta, min, max);
setter(num);
_data.Settings.ClampValues();
ApplySettingsToAI();
Save();
((MelonBase)this).LoggerInstance.Msg("Mimic " + label + " set to " + (percent ? ((num * 100f).ToString("F0") + "%") : num.ToString("F2")) + ".");
}
}
private void PrintLearningStats()
{
MimicLearning learning = _data.Learning;
((MelonBase)this).LoggerInstance.Msg("Mimic learning: sessions=" + learning.sessions + ", average movement speed=" + learning.AverageSpeed().ToString("F2") + ", average pause=" + learning.AveragePause().ToString("F2") + "s, average turn=" + learning.AverageTurn().ToString("F1") + " degrees, movement samples=" + learning.walkingBursts.Count + ", observations=" + learning.observations.Count + ". Data: " + _data.DirectoryPath);
}
private void PrintSettings()
{
MimicSettings settings = _data.Settings;
((MelonBase)this).LoggerInstance.Msg("Mimic settings: enabled=" + settings.enabled + ", mode=" + settings.behaviorMode.ToString() + ", stalking=" + settings.stalkingEnabled + ", attacks=" + settings.attacksEnabled + ", selfAware=" + settings.selfAwareEnabled + ", visualGlitches=" + settings.visualGlitchesEnabled + ", learning=" + settings.learningEnabled + ", stalkDistance=" + settings.stalkDistance.ToString("F1") + ", rushChance=" + (settings.rushChance * 100f).ToString("F0") + "%, attackCooldown=" + settings.attackCooldown.ToString("F1") + "s, jumpscareCooldown=" + settings.jumpscareCooldown.ToString("F1") + "s, TTS=" + settings.ttsEnabled + ".");
}
private void ApplySettingsToAI()
{
if (_ai != null && _data != null)
{
MimicSettings settings = _data.Settings;
settings.ClampValues();
_ai.Configure(settings, _data.Learning, _data.Encounters);
bool attacksEnabled = settings.attacksEnabled && settings.behaviorMode != MimicBehaviorMode.NonHostile && settings.behaviorMode != MimicBehaviorMode.Jumpscare;
bool coverEnabled = settings.stalkingEnabled && settings.behaviorMode != MimicBehaviorMode.RuthlessAttacker;
_ai.SetEnabled(settings.enabled);
_ai.SetCoverEnabled(coverEnabled);
_ai.SetAttacksEnabled(attacksEnabled);
_ai.SetSelfAware(settings.selfAwareEnabled);
_ai.SetVisualGlitches(settings.visualGlitchesEnabled);
}
}
private void ConfigureTTS()
{
if (_tts != null && _data != null)
{
_tts.Configure(_data.Settings.ttsEnabled, _data.Settings.ttsPitch, _data.Settings.ttsFrequency);
}
}
private void ListTTSVoices()
{
if (_tts == null)
{
return;
}
IReadOnlyList<string> installedVoices = _tts.GetInstalledVoices();
if (installedVoices.Count == 0)
{
((MelonBase)this).LoggerInstance.Warning("No installed Windows SAPI voices were returned, or SAPI enumeration failed.");
return;
}
((MelonBase)this).LoggerInstance.Msg("===== INSTALLED WINDOWS SPEECH VOICES =====");
for (int i = 0; i < installedVoices.Count; i++)
{
((MelonBase)this).LoggerInstance.Msg(i + ": " + installedVoices[i]);
}
((MelonBase)this).LoggerInstance.Msg("===========================================");
}
private void Save()
{
if (_data != null)
{
_data.Settings.ClampValues();
_data.SaveAll();
}
}
public override void OnSceneWasLoaded(int buildIndex, string sceneName)
{
_ai?.NotifySceneLoaded(sceneName);
}
public override void OnUpdate()
{
_ai?.Update();
if (_data != null && Time.unscaledTime >= _nextSaveAt)
{
_nextSaveAt = Time.unscaledTime + 30f;
Save();
}
}
public override void OnDeinitializeMelon()
{
Save();
_ai?.Dispose();
_tts?.Dispose();
_ai = null;
_tts = null;
_data = null;
((MelonBase)this).LoggerInstance.Msg("Mimic Mod shut down.");
}
}
}