Decompiled source of TheSyndicate v0.9.77
TheSyndicate.dll
Decompiled 3 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Microsoft.CodeAnalysis; using SOD.Common; using SOD.Common.Helpers; using SOD.Common.Helpers.DialogObjects; using SOD.Common.Helpers.GameplayObjects; using SOD.Common.Helpers.SyncDiskObjects; using TMPro; using TheSyndicate.Cards; using TheSyndicate.Contracts; using TheSyndicate.Cripple; using TheSyndicate.CruncherApp; using TheSyndicate.DebugTools; using TheSyndicate.Evidence; using TheSyndicate.Firearms; using TheSyndicate.Heat; using TheSyndicate.Mask; using TheSyndicate.Money; using TheSyndicate.News; using TheSyndicate.Patches; using TheSyndicate.Poison; using TheSyndicate.Progression; using TheSyndicate.Restraint; using TheSyndicate.State; using TheSyndicate.Supplies; using TheSyndicate.Tools; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using UnityStandardAssets.Characters.FirstPerson; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("TheSyndicate")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+8120a4f80c64d63eba2496c9f06e03dcc36c3195")] [assembly: AssemblyProduct("TheSyndicate")] [assembly: AssemblyTitle("TheSyndicate")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 TheSyndicate { [BepInPlugin("Kropath.TheSyndicate", "The Syndicate", "0.9.77")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BasePlugin { internal static ManualLogSource Logger; internal static Harmony HarmonyInstance; internal static void LogVerbose(string msg) { ConfigEntry<bool> verboseLogging = SyndicateConfig.VerboseLogging; if (verboseLogging != null && verboseLogging.Value) { Logger.LogInfo((object)msg); } } public override void Load() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected O, but got Unknown Logger = ((BasePlugin)this).Log; ManualLogSource logger = Logger; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(13, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("The Syndicate"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" v"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("0.9.77"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loading..."); } logger.LogInfo(val); SyndicateConfig.Init(((BasePlugin)this).Config); Rewards.Register(); SyndicateState.Hook(); InvestigationTimer.Hook(); Lib.Gameplay.OnVictimReported += delegate(object? sender, VictimReportedArgs args) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown try { CrimeSceneManager.OnBodyReported(((VictimKilledArgs)args).Victim, args.Reporter); } catch (Exception ex) { ManualLogSource logger3 = Logger; bool flag2 = default(bool); BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(45, 1, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[Syndicate] OnVictimReported handler failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message); } logger3.LogWarning(val2); } }; ConfigEntry<bool> debugEnabled = SyndicateConfig.DebugEnabled; if (debugEnabled != null && debugEnabled.Value) { ((BasePlugin)this).AddComponent<DebugHud>(); } ((BasePlugin)this).AddComponent<PrintCueOverlay>(); ((BasePlugin)this).AddComponent<SceneWatch>(); ((BasePlugin)this).AddComponent<WarrantHud>(); ((BasePlugin)this).AddComponent<SyndicateHud>(); HeatMeter.Source = () => SyndicateState.Data?.ResidualHeat ?? 0f; ((BasePlugin)this).AddComponent<FirearmsHotkey>(); ((BasePlugin)this).AddComponent<MaskHotkey>(); ((BasePlugin)this).AddComponent<NewsPageView>(); ((BasePlugin)this).AddComponent<FallHeaveHotkey>(); ((BasePlugin)this).AddComponent<EscortHotkey>(); ((BasePlugin)this).AddComponent<FirearmsHud>(); ((BasePlugin)this).AddComponent<PoseLab>(); TipLine.Init(); PauseLine.Init(); FirearmsPose.Init(); FirearmsAssetTuner.Init(); EchelonHomeGuard.Init(); ClassInjector.RegisterTypeInIl2Cpp<SyndicateAppContent>(); ClassInjector.RegisterTypeInIl2Cpp<SuppliesAppContent>(); HarmonyInstance = new Harmony("Kropath.TheSyndicate"); HarmonyInstance.PatchAll(typeof(Plugin).Assembly); ManualLogSource logger2 = Logger; val = new BepInExInfoLogInterpolatedStringHandler(26, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("The Syndicate"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loaded. "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(HarmonyInstance.GetPatchedMethods().Count()); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" methods patched."); } logger2.LogInfo(val); } } internal static class PluginInfo { public const string GUID = "Kropath.TheSyndicate"; public const string NAME = "The Syndicate"; public const string VERSION = "0.9.77"; } internal static class ProtectedCitizens { internal const string LandlordPresetName = "SelfEmployedLandlord"; private static readonly Dictionary<int, float> _lastToastByVictim = new Dictionary<int, float>(); internal static bool IsProtected(Human h) { if ((Object)(object)h == (Object)null) { return false; } try { ConfigEntry<bool> protectFiniteNPCs = SyndicateConfig.ProtectFiniteNPCs; return (protectFiniteNPCs == null || protectFiniteNPCs.Value) && ((Actor)h).isEnforcer; } catch { return true; } } internal static bool IsContractExcluded(Human h) { if ((Object)(object)h == (Object)null) { return false; } if (!IsProtected(h)) { ConfigEntry<bool> protectFiniteNPCs = SyndicateConfig.ProtectFiniteNPCs; if ((protectFiniteNPCs != null && !protectFiniteNPCs.Value) || !IsLandlord(h)) { return IsLastCompanyWorker(h); } } return true; } internal static bool IsLandlord(Human h) { try { object obj; if (h == null) { obj = null; } else { Occupation job = h.job; obj = ((job != null) ? job.preset : null); } OccupationPreset val = (OccupationPreset)obj; return (SoCustomComparison)(object)val != (SoCustomComparison)null && ((Object)val).name == "SelfEmployedLandlord"; } catch { return false; } } internal static bool IsLastCompanyWorker(Human h) { ConfigEntry<bool> protectLastCompanyWorker = SyndicateConfig.ProtectLastCompanyWorker; if (protectLastCompanyWorker != null && !protectLastCompanyWorker.Value) { return false; } try { if ((Object)(object)h == (Object)null) { return false; } if (((Actor)h).isEnforcer || IsLandlord(h)) { return false; } Occupation job = h.job; object obj; if (job == null) { obj = null; } else { Company employer = job.employer; obj = ((employer != null) ? employer.companyRoster : null); } List<Occupation> val = (List<Occupation>)obj; if (val == null) { return false; } bool result = false; Enumerator<Occupation> enumerator = val.GetEnumerator(); while (enumerator.MoveNext()) { Occupation current = enumerator.Current; Human val2 = ((current != null) ? current.employee : null); if (!((Object)(object)val2 == (Object)null)) { if (val2.humanID == h.humanID) { result = true; } else if (!((Actor)val2).isDead) { return false; } } } return result; } catch { return true; } } internal static void DegradeToKO(Citizen target) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_0031: 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) try { if ((Object)(object)target != (Object)null && (Object)(object)((Actor)target).ai != (Object)null && !((Actor)target).ai.ko) { ((Actor)target).ai.SetKO(true, ((Component)target).transform.position, Vector3.up, false, 0f, true, 1f); } } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(36, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Protect] DegradeToKO SetKO failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } logger.LogWarning(val); } try { int key = (((Object)(object)target != (Object)null) ? ((Human)target).humanID : (-1)); float time = Time.time; if (_lastToastByVictim.TryGetValue(key, out var value) && time - value < 3f) { return; } _lastToastByVictim[key] = time; } catch { } try { MessageQueue.Enqueue(RefusalLine((Human)(object)target)); } catch { } } internal static string RefusalLine(Human h) { try { if ((Object)(object)h != (Object)null && ((Actor)h).isEnforcer) { return "Enforcers stay breathing: the Syndicate needs the law predictable. Down, not dead."; } if (IsLandlord(h)) { return "Kill the landlord and the whole block goes to hell. Down, not dead."; } } catch { } return "This one's off-limits: the Syndicate wants them alive. Down, not dead."; } } internal static class SyndicateConfig { public static ConfigEntry<int> WitnessReportMinHours; public static ConfigEntry<int> WitnessReportMaxHours; public static ConfigEntry<int> WelfareMinHours; public static ConfigEntry<int> WelfareMaxHours; public static ConfigEntry<int> SceneColdHours; public static ConfigEntry<int> WitnessInterviewHours; public static ConfigEntry<int> WarrantResolveHours; public static ConfigEntry<int> WarrantDurationHours; public static ConfigEntry<int> FingerprintIdHeatThreshold; public static ConfigEntry<int> FingerprintIdCountThreshold; public static ConfigEntry<int> FingerprintMinHeat; public static ConfigEntry<int> FingerprintMaxHeat; public static ConfigEntry<bool> SuppressNativePlayerPrints; public static ConfigEntry<float> PlayerPrintExpiryDays; public static ConfigEntry<bool> CrossTimelineHealEnabled; public static ConfigEntry<float> CrossTimelineBackJumpHours; public static ConfigEntry<bool> ResidualHeatEnabled; public static ConfigEntry<float> ResidualHeatPerMurderMax; public static ConfigEntry<float> HeistOfferThreshold; public static ConfigEntry<int> ResidualFinePerMurder; public static ConfigEntry<bool> ResidualWarrantAt100; public static ConfigEntry<bool> HeistEnabled; public static ConfigEntry<int> HeistBackroomPercent; public static ConfigEntry<string> HeistFolderPreset; public static ConfigEntry<bool> NewsEnabled; public static ConfigEntry<int> NewsFreshHours; public static ConfigEntry<bool> NewsBackPages; public static ConfigEntry<int> NewsRoundupWindowDays; public static ConfigEntry<int> NewsRoundupMinKills; public static ConfigEntry<bool> NewsSecondaryEnabled; public static ConfigEntry<bool> NewsSerialEnabled; public static ConfigEntry<int> NewsSerialWindowDays; public static ConfigEntry<int> NewsSerialSuspectCount; public static ConfigEntry<int> NewsSerialPanicCount; public static ConfigEntry<int> NewsBuildingClusterMin; public static ConfigEntry<int> NewsDistrictWaveMin; public static ConfigEntry<bool> MaskNotorietyEnabled; public static ConfigEntry<float> MaskNotorietyDecayPerDay; public static ConfigEntry<float> MaskPublicWearPerHour; public static ConfigEntry<float> MaskIncidentBase; public static ConfigEntry<float> MaskIncidentMax; public static ConfigEntry<float> MaskWitnessWeight; public static ConfigEntry<float> MaskPublicPlaceMult; public static ConfigEntry<float> MaskDaytimeMult; public static ConfigEntry<float> MaskNerveDrainMult; public static ConfigEntry<int> MaskRedisguiseCost; public static ConfigEntry<float> MaskRedisguiseDrop; public static ConfigEntry<bool> MaskRedisguiseClearsHistory; public static ConfigEntry<float> MaskMergeMinColdShare; public static ConfigEntry<int> MaskMergeFineBasePercent; public static ConfigEntry<int> MaskMergeFineNotorietyPercent; public static ConfigEntry<bool> MaskBustedDestroysMask; public static ConfigEntry<bool> MaskEnforcerSoftStopEnabled; public static ConfigEntry<float> MaskEnforcerSoftStopThreshold; public static ConfigEntry<float> MaskEnforcerSoftStopRange; public static ConfigEntry<float> MaskEnforcerSoftStopCooldownSeconds; public static ConfigEntry<bool> MaskNonLethalEnabled; public static ConfigEntry<float> MaskNonLethalIncidentMax; public static ConfigEntry<float> MaskNonLethalCooldownSeconds; public static ConfigEntry<float> MaskNonLethalAssaultMult; public static ConfigEntry<float> MaskNonLethalBreakingMult; public static ConfigEntry<float> MaskNonLethalTheftMult; public static ConfigEntry<float> MaskNonLethalTrespassMult; public static ConfigEntry<bool> CallingCardEnabled; public static ConfigEntry<int> CallingCardMinLevel; public static ConfigEntry<int> CallingCardPrice; public static ConfigEntry<int> CallingCardMonikerMaxLength; public static ConfigEntry<float> CallingCardResidualFraction; public static ConfigEntry<int> CallingCardSecondUnmaskHeat; public static ConfigEntry<int> CallingCardReauthorFee; public static ConfigEntry<bool> CallingCardRaidRevealEnabled; public static ConfigEntry<bool> RepairEchelonFloors; public static ConfigEntry<bool> CrippleEnabled; public static ConfigEntry<float> CrippleSpeedMultiplier; public static ConfigEntry<float> CrippleDurationSeconds; public static ConfigEntry<bool> CrippleBloodTrailEnabled; public static ConfigEntry<bool> CamerasIdentify; public static ConfigEntry<int> CameraNearSceneHeat; public static ConfigEntry<int> CameraNearSceneFreshWindowHours; public static ConfigEntry<bool> CameraSweepEnabled; public static ConfigEntry<bool> CamerasRespectDarkness; public static ConfigEntry<bool> HudClusterEnabled; public static ConfigEntry<bool> HudSceneTableEnabled; public static ConfigEntry<HuntMode> HuntMode; public static ConfigEntry<float> PursuitResponseRange; public static ConfigEntry<float> PursuitMaxDistanceMeters; public static ConfigEntry<bool> PursuitRequireLineOfSight; public static ConfigEntry<bool> RequirePlayerInsideScene; public static ConfigEntry<bool> BodyDiscoveryRequireLineOfSight; public static ConfigEntry<bool> BatchReportScopeEnabled; public static ConfigEntry<bool> WelfareRaidEnabled; public static ConfigEntry<float> WelfareRaidDelayHours; public static ConfigEntry<bool> WelfareRaidForceCrimeScene; public static ConfigEntry<int> MurderFineAmount; public static ConfigEntry<float> IllegalRefreshSeconds; public static ConfigEntry<bool> StakeoutEnabled; public static ConfigEntry<float> HomeRaidDelayHours; public static ConfigEntry<bool> StakeoutForceCrimeScene; public static ConfigEntry<float> StakeoutGuardHours; public static ConfigEntry<int> RepPenaltyOnIdentifiedContract; public static ConfigEntry<int> RepPenaltyOnIdentifiedRandom; public static ConfigEntry<int> HitmanLevelPenaltyOnIdentified; public static ConfigEntry<float> RepMessageDelaySeconds; public static ConfigEntry<int> RepPenaltyOnContractCancel; public static ConfigEntry<bool> ContractBuildingSpreadEnabled; public static ConfigEntry<int> ContractSpreadSameBuildingPenalty; public static ConfigEntry<int> ContractSpreadSameFloorPenalty; public static ConfigEntry<bool> CaseBoardEnabled; public static ConfigEntry<int> AutoCollectHours; public static ConfigEntry<bool> TipLineEnabled; public static ConfigEntry<int> TipLineNumber; public static ConfigEntry<bool> PauseLineEnabled; public static ConfigEntry<int> PauseLineNumber; public static ConfigEntry<int> AutoAssignMaxLevel; public static ConfigEntry<int> MaxConcurrentContractsL3; public static ConfigEntry<int> OfferBoardSize; public static ConfigEntry<int> RefreshBoardCooldownHours; public static ConfigEntry<int> OfferLifetimeHours; public static ConfigEntry<int> ContractCooldownHours; public static ConfigEntry<int> FirstContractDelayHours; public static ConfigEntry<int> DeadlineHours; public static ConfigEntry<bool> CloseToastEnabled; public static ConfigEntry<bool> OnboardingEnabled; public static ConfigEntry<int> FirstJobAfterActivationHours; public static ConfigEntry<int> CheckInReminder1Minutes; public static ConfigEntry<int> CheckInReminder2Minutes; public static ConfigEntry<bool> MoleEmailEnabled; public static ConfigEntry<bool> ClearTrespassOnResolve; public static ConfigEntry<int> ContractBasePayout; public static ConfigEntry<float> DiffMultT2; public static ConfigEntry<float> DiffMultT3; public static ConfigEntry<float> InfoMultPartial; public static ConfigEntry<float> InfoMultCold; public static ConfigEntry<int> InfoFullPctT1; public static ConfigEntry<int> InfoPartialPctT1; public static ConfigEntry<int> InfoFullPctT2; public static ConfigEntry<int> InfoPartialPctT2; public static ConfigEntry<int> InfoFullPctT3; public static ConfigEntry<int> InfoPartialPctT3; public static ConfigEntry<int> InvestigationBias; public static ConfigEntry<int> ObjChanceZero; public static ConfigEntry<int> ObjChanceOne; public static ConfigEntry<float> BonusClean; public static ConfigEntry<float> BonusDeadline; public static ConfigEntry<float> BonusPhoto; public static ConfigEntry<float> BonusSuicide; public static ConfigEntry<bool> EnablePhotoObjective; public static ConfigEntry<bool> EnableSuicideObjective; public static ConfigEntry<bool> EnableDisposalObjective; public static ConfigEntry<int> DisposalContractPercent; public static ConfigEntry<float> DisposalPenalty; public static ConfigEntry<int> DisposalMinLevel; public static ConfigEntry<string> DisposalWeaponList; public static ConfigEntry<bool> EnableFallForge; public static ConfigEntry<int> FallVariantPercent; public static ConfigEntry<bool> FallKillsEnabled; public static ConfigEntry<float> LethalFallHeight; public static ConfigEntry<KeyCode> HeaveKey; public static ConfigEntry<float> HeaveSpeed; public static ConfigEntry<float> DragLiftAssist; public static ConfigEntry<bool> FallSceneAtLanding; public static ConfigEntry<bool> FallAntiSnagEnabled; public static ConfigEntry<float> FallAntiSnagForce; public static ConfigEntry<bool> HomeVacateEnabled; public static ConfigEntry<int> HomeVacateDelayHours; public static ConfigEntry<bool> BodyCleanupEnabled; public static ConfigEntry<int> BodyCleanupDelayHours; public static ConfigEntry<float> KODurationMultiplier; public static ConfigEntry<float> BluntKOMultiplier; public static ConfigEntry<float> BladeKOMultiplier; public static ConfigEntry<int> Level1Rep; public static ConfigEntry<int> Level2Rep; public static ConfigEntry<int> Level3Rep; public static ConfigEntry<int> Level1Reward; public static ConfigEntry<int> Level2Reward; public static ConfigEntry<int> Level3Reward; public static ConfigEntry<string> RewardDiskPool; public static ConfigEntry<bool> SkipCampaignEnabled; public static ConfigEntry<int> SocialCreditPerToken; public static ConfigEntry<int> SocialCreditPurchaseMoneyCost; public static ConfigEntry<int> FineMinimumBalance; public static ConfigEntry<int> FineEarlyPayoffPercent; public static ConfigEntry<bool> RaidWeaponSeizureEnabled; public static ConfigEntry<int> RaidWeaponSurchargePercent; public static ConfigEntry<bool> CarryWeaponSeizureEnabled; public static ConfigEntry<int> CarryWeaponSurchargePercent; public static ConfigEntry<bool> CruncherRaidPenaltyEnabled; public static ConfigEntry<int> CruncherRaidSurchargePercent; public static ConfigEntry<int> CruncherRaidRepLoss; public static ConfigEntry<bool> SafehouseBreachEnabled; public static ConfigEntry<bool> SuppliesEnabled; public static ConfigEntry<int> SuppliesDiscountPercent; public static ConfigEntry<int> SyncDiskAppPrice; public static ConfigEntry<int> MaskDiskAppPrice; public static ConfigEntry<bool> RefundLegacyDiskDuds; public static ConfigEntry<int> SuppliesDeliveryHours; public static ConfigEntry<bool> GroceriesEnabled; public static ConfigEntry<int> GroceriesMarkupPercent; public static ConfigEntry<float> GearPriceMultiplier; public static ConfigEntry<float> AmmoPriceMultiplier; public static ConfigEntry<float> GunPriceMultiplier; public static ConfigEntry<bool> FirearmsEnabled; public static ConfigEntry<bool> FirearmsHeadshotLethal; public static ConfigEntry<float> FirearmsHeadshotRadius; public static ConfigEntry<bool> FirearmsHeadshotDebug; public static ConfigEntry<string> FirearmsOneShotWeapons; public static ConfigEntry<float> FirearmsAccuracy; public static ConfigEntry<float> FirearmsMinCooldownSeconds; public static ConfigEntry<bool> FirearmsConsumeAmmo; public static ConfigEntry<bool> FirearmsSpawnCasings; public static ConfigEntry<KeyCode> FirearmsFireKey; public static ConfigEntry<KeyCode> FirearmsMeleeKey; public static ConfigEntry<float> FirearmsRecoil; public static ConfigEntry<bool> FirearmsPoseLab; public static ConfigEntry<KeyCode> FirearmsReloadKey; public static ConfigEntry<float> FirearmsReloadSeconds; public static ConfigEntry<string> FirearmsMagazineSizes; public static ConfigEntry<string> FirearmsAmmoBoxSizes; public static ConfigEntry<bool> FirearmsAmmoHud; public static ConfigEntry<string> FirearmsAutoFireWeapons; public static ConfigEntry<float> FirearmsAutoFireInterval; public static ConfigEntry<float> FirearmsAutoRecoilScale; public static ConfigEntry<int> FirearmsShotgunPellets; public static ConfigEntry<float> FirearmsShotgunSpread; public static ConfigEntry<int> FirearmsShotgunLethalPellets; public static ConfigEntry<KeyCode> FirearmsShotgunAmmoKey; public static ConfigEntry<bool> SniperZoomEnabled; public static ConfigEntry<string> SniperZoomWeapons; public static ConfigEntry<float> SniperZoomDefault; public static ConfigEntry<float> SniperZoomMin; public static ConfigEntry<float> SniperZoomMax; public static ConfigEntry<int> SniperZoomStepPercent; public static ConfigEntry<float> SniperZoomEaseSpeed; public static ConfigEntry<bool> SniperZoomScalesSensitivity; public static ConfigEntry<float> SniperScopedRecoilScale; public static ConfigEntry<bool> SniperZoomReadout; public static ConfigEntry<bool> FirearmsDrawnDread; public static ConfigEntry<float> FirearmsDrawnNerve; public static ConfigEntry<float> FirearmsDrawnRange; public static ConfigEntry<bool> EnforcersUnfazedByDrawnGuns; public static ConfigEntry<float> FirearmsBulletRangeFloor; public static ConfigEntry<bool> FirearmsGunshotsAlarm; public static ConfigEntry<string> FirearmsAlarmExemptGuns; public static ConfigEntry<float> FirearmsGunshotSpook; public static ConfigEntry<bool> FirearmsRoomTurnOnHit; public static ConfigEntry<float> FirearmsRoomTurnRange; public static ConfigEntry<float> FirearmsInteractYieldDistance; public static ConfigEntry<KeyCode> MaskToggleKey; public static ConfigEntry<bool> MaskScareEnabled; public static ConfigEntry<float> MaskScareNerve; public static ConfigEntry<float> MaskScareRange; public static ConfigEntry<bool> MaskScareEnforcers; public static ConfigEntry<int> MaskedDescriptionHeat; public static ConfigEntry<bool> MaskUnmaskInViewIdentifies; public static ConfigEntry<bool> PoisonEnabled; public static ConfigEntry<bool> PoisonPromptOverride; public static ConfigEntry<int> SyringeDoses; public static ConfigEntry<int> VialDoses; public static ConfigEntry<int> SyringePrice; public static ConfigEntry<int> VialPrice; public static ConfigEntry<int> FingerprintRemoverCharges; public static ConfigEntry<string> FingerprintRemoverPreset; public static ConfigEntry<float> FingerprintRemoverRange; public static ConfigEntry<string> FingerprintNoPrintPresets; public static ConfigEntry<bool> ChloroformEnabled; public static ConfigEntry<string> ChloroformPreset; public static ConfigEntry<float> ChloroformRange; public static ConfigEntry<float> ChloroformBehindDot; public static ConfigEntry<int> ChloroformDoses; public static ConfigEntry<bool> SedatePromptOverride; public static ConfigEntry<float> HandcuffDurationHours; public static ConfigEntry<bool> CuffedKOKillEnabled; public static ConfigEntry<bool> DuctTapeEnabled; public static ConfigEntry<string> DuctTapePreset; public static ConfigEntry<int> DuctTapeCharges; public static ConfigEntry<float> DuctTapeRestrainHours; public static ConfigEntry<float> DuctTapeRangeMeters; public static ConfigEntry<float> DuctTapeBehindDot; public static ConfigEntry<bool> TapePromptOverride; public static ConfigEntry<float> DuctTapeBindSeconds; public static ConfigEntry<bool> RestraintPublicReactionEnabled; public static ConfigEntry<bool> RestraintPublicEnforcersOnly; public static ConfigEntry<float> RestraintPublicCallDelayHours; public static ConfigEntry<int> RestraintCaughtHeat; public static ConfigEntry<bool> EscortEnabled; public static ConfigEntry<KeyCode> EscortHotkey; public static ConfigEntry<float> EscortFollowDistance; public static ConfigEntry<float> EscortSpeed; public static ConfigEntry<float> EscortMaxDistance; public static ConfigEntry<float> EscortPlayerDragSpeed; public static ConfigEntry<bool> EnableNoViolenceObjective; public static ConfigEntry<int> NoViolenceChancePercent; public static ConfigEntry<float> BonusNoViolence; public static ConfigEntry<int> ForgeViolenceHeat; public static ConfigEntry<float> PoisonRange; public static ConfigEntry<float> PoisonCooldownSeconds; public static ConfigEntry<float> SyringeKillDelayGameMinutes; public static ConfigEntry<float> SyringeStaggerDrunk; public static ConfigEntry<int> ForgeDelayMinutes; public static ConfigEntry<bool> EnablePoisonObjective; public static ConfigEntry<float> BonusPoison; public static ConfigEntry<bool> ProtectFiniteNPCs; public static ConfigEntry<bool> ProtectLastCompanyWorker; public static ConfigEntry<float> SightCheckIntervalSeconds; public static ConfigEntry<bool> VerboseLogging; public static ConfigEntry<float> MessageGapSeconds; public static ConfigEntry<bool> DebugEnabled; public static void Init(ConfigFile cfg) { WitnessReportMinHours = cfg.Bind<int>("Manhunt", "WitnessReportMinHours", 2, "Minimum in-game hours a witness takes to reach the authorities and report. Kill/KO them before this to cancel it."); WitnessReportMaxHours = cfg.Bind<int>("Manhunt", "WitnessReportMaxHours", 2, "Maximum in-game hours a witness takes to report."); WelfareMinHours = cfg.Bind<int>("Manhunt", "WelfareMinHours", 8, "Minimum in-game hours before an unseen body is found by a welfare check. Kept short so an undiscovered body is found, and then cleaned up, sooner rather than lingering for days."); WelfareMaxHours = cfg.Bind<int>("Manhunt", "WelfareMaxHours", 12, "Maximum in-game hours before a welfare check finds an unseen body."); SceneColdHours = cfg.Bind<int>("Manhunt", "SceneColdHours", 8, "In-game hours a discovered-but-unsolved scene stays open. If no witness reports in time, forensics decide, then it goes cold."); WitnessInterviewHours = cfg.Bind<int>("Manhunt", "WitnessInterviewHours", 2, "Once a body is officially reported, pending witnesses who can name the player are interviewed within this many in-game hours (kill/KO them first to prevent it)."); WarrantResolveHours = cfg.Bind<int>("Manhunt", "WarrantResolveHours", 2, "Grace hours after a positive ID before the warrant/hunt actually begins, a heads-up window, NOT an escape: the warrant issues regardless of where you are."); WarrantDurationHours = cfg.Bind<int>("Manhunt", "WarrantDurationHours", 48, "How long a warrant stays active once issued."); FingerprintIdHeatThreshold = cfg.Bind<int>("Evidence", "FingerprintIdHeatThreshold", 25, "Total fingerprint heat at a scene that alone makes a positive forensic ID."); FingerprintIdCountThreshold = cfg.Bind<int>("Evidence", "FingerprintIdCountThreshold", 4, "Number of distinct fingerprinted objects at a scene that alone makes a positive forensic ID."); FingerprintMinHeat = cfg.Bind<int>("Evidence", "FingerprintMinHeat", 3, "Minimum random heat weight assigned to each fingerprint left."); FingerprintMaxHeat = cfg.Bind<int>("Evidence", "FingerprintMaxHeat", 12, "Maximum random heat weight assigned to each fingerprint left."); SuppressNativePlayerPrints = cfg.Bind<bool>("Evidence", "SuppressNativePlayerPrints", true, "Hide the game's always-on player fingerprint from the vanilla scanner on items you own/carry (weapons, casings, delivered supplies). This static print ignores gloves; suppressing it means only the mod's own gloves-gated print system marks you. Leaves NPC prints (incl. the fixer who mailed a weapon) intact. Disable to restore vanilla behaviour."); PlayerPrintExpiryDays = cfg.Bind<float>("Evidence", "PlayerPrintExpiryDays", 3f, "In-game days after which a fingerprint you left goes cold and is forgotten by the mod's forensic tally (bounds save-file growth; important for Steam Cloud sync). A body discovered after this window is no longer incriminated by those prints. The game's own scannable print already fades on its own; this only prunes the mod's record. 0 = never expire (unbounded)."); CrossTimelineHealEnabled = cfg.Bind<bool>("Integrity", "CrossTimelineHealEnabled", true, "Heal cross-timeline save contamination (starting a new game on the same city + save name leaks the previous playthrough's records into the new save). On load it drops only records whose timestamp is in the FUTURE of the current clock: the phantom 100+ hour crime scene, a phantom outstanding fine, and a jammed far-future contract timer. STRICTLY NON-DESTRUCTIVE: it never wipes the save (a future-dated record is provably a foreign leftover; legitimate state is never ahead of 'now'). Disable only to debug."); CrossTimelineBackJumpHours = cfg.Bind<float>("Integrity", "CrossTimelineBackJumpHours", 48f, "Diagnostic only: how many in-game hours the loaded game clock must be BEHIND the mod's last save before a one-line warning is logged (a reused-name new game, or a Steam-Cloud .sodb rollback). Purely informational: no state is reset either way; the per-record heal neutralises any foreign leftovers regardless."); ResidualHeatEnabled = cfg.Bind<bool>("Heat", "ResidualHeatEnabled", true, "F2 cumulative residual-evidence meter: when a murder's case goes COLD but you still left sub-threshold evidence, the top-centre HEAT bar climbs. At ~80% the Syndicate offers a clearing heist; at 100% the law catches up. Turn off to freeze the meter at 0."); ResidualHeatPerMurderMax = cfg.Bind<float>("Heat", "ResidualHeatPerMurderMax", 0.2f, "The MOST a single cold-but-messy murder can add to the residual-evidence meter (0.20 = 20%). The actual add is this times how close the scene came to identifying you (its fingerprint heat/count vs the ID thresholds)."); HeistOfferThreshold = cfg.Bind<float>("Heat", "HeistOfferThreshold", 0.8f, "At this meter level (0.80 = 80%) the Syndicate v-mails you a tip and opens the 'Loose Ends' heist to clear it. Clamped 0.10-0.95."); ResidualFinePerMurder = cfg.Bind<int>("Heat", "ResidualFinePerMurder", 1000, "At 100% the law catches up: this fine (credits) is levied for EACH murder that fed the meter. Half a full-ID murder fine by default (residual evidence is weaker than a positive ID)."); ResidualWarrantAt100 = cfg.Bind<bool>("Heat", "ResidualWarrantAt100", true, "At 100%, also issue a single warrant (enforcers hunt you) alongside the fines. Off = fines only."); HeistEnabled = cfg.Bind<bool>("Heist", "Enabled", true, "Enable the 'Loose Ends' evidence heist offered at ~80% meter. Off = no offer/heist (the meter still climbs to the 100% punishment, which becomes the only outcome)."); HeistBackroomPercent = cfg.Bind<int>("Heist", "BackroomPercent", 80, "Chance (%) the evidence folder spawns in the enforcer division office's password-protected secure BACKROOM (the highest-security room in the office). Otherwise it lands on a desk deeper in the office (never reception). Clamped 0-100. The folder always spawns in a division office now, never a home."); HeistFolderPreset = cfg.Bind<string>("Heist", "FolderPreset", "SealedEnvelope", "Item preset spawned as the stealable evidence folder. Default 'SealedEnvelope' = the in-game 'Secret Envelope'. Blank = auto (prefer a carryable documents item, else a briefcase). Set another PascalCase preset name to override. Use the 'Find secret-envelope-like presets' debug button to discover names."); NewsEnabled = cfg.Bind<bool>("News", "Enabled", true, "Enable The Syndicate's custom front-page stories in the in-game newspaper (murder/manhunt/heist beats named with the real victim and location) and the matching city news-ticker headline. Off = the vanilla paper only."); NewsFreshHours = cfg.Bind<int>("News", "FreshHours", 72, "How long (in-game hours) a Syndicate news beat stays eligible to run as a front-page CANDIDATE. After this it drops off the front page but (Newspaper v2) remains readable on the scroll-back archive pages until it ages off the event ring. Newspaper v2 raised the default 48 -> 72."); NewsBackPages = cfg.Bind<bool>("News", "BackPages", true, "Newspaper v2: while reading a raised newspaper, scroll the mouse wheel to flip to older/overflow news (page 0 = the live edition; scroll down for earlier stories). Off = only the live front page, no scroll-back (the front-page priority/preservation changes still apply)."); NewsRoundupWindowDays = cfg.Bind<int>("News", "RoundupWindowDays", 3, "Window (in-game days) the 'crime wave' round-up story counts recent killings over."); NewsRoundupMinKills = cfg.Bind<int>("News", "RoundupMinKills", 3, "Minimum distinct victims within the round-up window before the 'crime wave' story runs instead of a single-victim story."); NewsSecondaryEnabled = cfg.Bind<bool>("News", "SecondaryStory", true, "Allow a second Syndicate beat to occupy the newspaper's article-2 slot when two stories are live. Off = only the top story is injected and any displaced vanilla lead keeps slot 2."); NewsSerialEnabled = cfg.Bind<bool>("News", "SerialProfiler", true, "Enable the locked serial-killer profiler: once enough killings share a distinctive trait (employer, home building, occupation, distinctive hair colour, or a whitelisted pair like gender+district), the press locks onto it and names a pattern ('the Kaizen Sushi murders') that persists across the front page and any WANTED coverage. Off = no serial narrative (cluster/district-wave/callback stories are unaffected)."); NewsSerialWindowDays = cfg.Bind<int>("News", "SerialWindowDays", 7, "Window (in-game days) of recent kills considered for the serial-profiler's eligible set."); NewsSerialSuspectCount = cfg.Bind<int>("News", "SerialSuspectCount", 3, "Minimum distinct victims sharing a trait before the press locks onto a serial pattern ('A PATTERN IN THE KILLINGS?')."); NewsSerialPanicCount = cfg.Bind<int>("News", "SerialPanicCount", 5, "Distinct matching victims at which the coverage escalates to city-wide panic ('SERIAL KILLER STALKS...')."); NewsBuildingClusterMin = cfg.Bind<int>("News", "BuildingClusterMinBodies", 2, "Minimum distinct bodies found at the SAME building within the round-up window before a same-building cluster story runs."); NewsDistrictWaveMin = cfg.Bind<int>("News", "DistrictWaveMinKills", 3, "Minimum distinct victims within the same DISTRICT (within the round-up window) before a district-wide crime-wave story runs."); MaskNotorietyEnabled = cfg.Bind<bool>("MaskNotoriety", "Enabled", true, "Master switch for the masked-persona notoriety system (meter, HUD bar, gradient effects, merge-on-slip, busted reveal). Off = masked crimes behave as before F7."); MaskNotorietyDecayPerDay = cfg.Bind<float>("MaskNotoriety", "DecayPerDay", 0.08f, "Notoriety lost per in-game day of lying low (not being seen committing a masked crime). 0.08 ≈ a maxed persona fades in ~12-13 days."); MaskPublicWearPerHour = cfg.Bind<float>("MaskNotoriety", "PublicWearPerHour", 0.02f, "Notoriety added per in-game HOUR while masked in a PUBLIC place (street/lobby) with a living conscious person present (×DaytimeMult in daytime, reusing that same knob). Runs alongside, not instead of, the per-incident bump above, so simply being seen wearing the mask around town slowly builds notoriety even between incidents; a quick masked job in and out costs only a fraction of a percent from this. 0 = off."); MaskIncidentBase = cfg.Bind<float>("MaskNotoriety", "IncidentBase", 0.15f, "Base notoriety added per masked crime that gets seen (before brazenness scaling)."); MaskIncidentMax = cfg.Bind<float>("MaskNotoriety", "IncidentMax", 0.4f, "Cap on the notoriety added by a single masked incident after brazenness scaling."); MaskWitnessWeight = cfg.Bind<float>("MaskNotoriety", "WitnessWeight", 0.15f, "+fraction per witness beyond the first (capped at 4 extra), multiplying the incident bump."); MaskPublicPlaceMult = cfg.Bind<float>("MaskNotoriety", "PublicPlaceMult", 1.5f, "Multiplier when the masked crime is in a public place (street / lobby / common area)."); MaskDaytimeMult = cfg.Bind<float>("MaskNotoriety", "DaytimeMult", 1.25f, "Multiplier when the masked crime happens in daytime (~07:00–21:00)."); MaskNerveDrainMult = cfg.Bind<float>("MaskNotoriety", "NerveDrainMult", 4f, "Extra nerve-drain multiplier at full notoriety for civilians who SEE the masked player (they panic/flee sooner). Scales from 0 (base ambient dread only) up to this at N=1."); MaskRedisguiseCost = cfg.Bind<int>("MaskNotoriety", "RedisguiseCost", 2500, "Cost of a 'Persona Wipe' (new disguise) from the Supplies app, which drops notoriety."); MaskRedisguiseDrop = cfg.Bind<float>("MaskNotoriety", "RedisguiseDrop", 1f, "Notoriety removed per Persona Wipe (1.0 = full reset)."); MaskRedisguiseClearsHistory = cfg.Bind<bool>("MaskNotoriety", "RedisguiseClearsHistory", false, "If true, a Persona Wipe also erases the masked-incident case files (a later slip can't merge old crimes). Default false = the street cools but the case files remain."); MaskMergeMinColdShare = cfg.Bind<float>("MaskNotoriety", "MergeMinColdShare", 0.25f, "Share of the COLD masked spree merged onto you on a slip at zero notoriety (scales to 100% at full notoriety), newest-first."); MaskMergeFineBasePercent = cfg.Bind<int>("MaskNotoriety", "MergeFineBasePercent", 50, "Per-merged-cold-incident fine as a % of the standard murder fine, at zero notoriety."); MaskMergeFineNotorietyPercent = cfg.Bind<int>("MaskNotoriety", "MergeFineNotorietyPercent", 50, "Additional per-incident fine %, scaled by notoriety (so at N=1 the fine = Base+this % of the murder fine)."); MaskBustedDestroysMask = cfg.Bind<bool>("MaskNotoriety", "BustedDestroysMask", true, "If true, getting busted while masked confiscates (uninstalls) the mask disk. You must acquire a new one."); MaskEnforcerSoftStopEnabled = cfg.Bind<bool>("MaskNotoriety", "EnforcerSoftStopEnabled", true, "At high notoriety, enforcers who SEE the masked player give a soft 'stop and look' (heightened suspicion, NOT a chase). Fires off raw line-of-sight, thresholded/range-capped/per-enforcer-cooldowned, and never overrides a real warrant pursuit. Disable to turn it off entirely."); MaskEnforcerSoftStopThreshold = cfg.Bind<float>("MaskNotoriety", "EnforcerSoftStopThreshold", 0.5f, "Minimum notoriety before enforcers start eyeing the mask (soft-stop tier)."); MaskEnforcerSoftStopRange = cfg.Bind<float>("MaskNotoriety", "EnforcerSoftStopRange", 12f, "Metres within which a seeing enforcer reacts to the notorious mask (kept tight vs the 50m warrant pursuit)."); MaskEnforcerSoftStopCooldownSeconds = cfg.Bind<float>("MaskNotoriety", "EnforcerSoftStopCooldownSeconds", 90f, "Per-enforcer cooldown between soft-stop looks."); MaskNonLethalEnabled = cfg.Bind<bool>("MaskNotoriety", "NonLethalEnabled", true, "If true, a SEEN non-lethal crime (assault, breaking & entering, theft, trespassing) committed while masked also bumps notoriety, scaled by severity. Off = only masked kills build notoriety, as before F7b."); MaskNonLethalIncidentMax = cfg.Bind<float>("MaskNotoriety", "NonLethalIncidentMax", 0.12f, "Cap on the notoriety added by a single non-lethal masked incident after scaling, kept well under IncidentMax (the masked-kill cap) so a spree of petty crime can never out-pace an actual killing."); MaskNonLethalCooldownSeconds = cfg.Bind<float>("MaskNotoriety", "NonLethalCooldownSeconds", 20f, "Per-crime-type cooldown (keyed by crime + victim/object/address) that folds a punch flurry or a theft spree into a single bump instead of one per swing/grab. 0 = no cooldown (bump every time)."); MaskNonLethalAssaultMult = cfg.Bind<float>("MaskNotoriety", "NonLethalAssaultMult", 0.4f, "Assault's fraction of a masked kill's base notoriety add (before the NonLethalIncidentMax cap). Highest of the four: decked in view of a witness reads almost as brazen as a kill."); MaskNonLethalBreakingMult = cfg.Bind<float>("MaskNotoriety", "NonLethalBreakingAndEnteringMult", 0.22f, "Breaking & entering's fraction of a masked kill's base notoriety add."); MaskNonLethalTheftMult = cfg.Bind<float>("MaskNotoriety", "NonLethalTheftMult", 0.15f, "Theft's fraction of a masked kill's base notoriety add."); MaskNonLethalTrespassMult = cfg.Bind<float>("MaskNotoriety", "NonLethalTrespassingMult", 0.08f, "Trespassing's fraction of a masked kill's base notoriety add, lowest of the four, just being somewhere you shouldn't."); CallingCardEnabled = cfg.Bind<bool>("CallingCard", "Enabled", true, "Master switch for the Calling Card: buy blank cards, sign a moniker at the cruncher, and leave one at a kill so the press names the killer by YOUR moniker (overriding the auto serial-killer epithet). Getting a carded case attributed to you (an 'unmasking') burns that moniker forever. Off = no cards, no claims, no burns."); CallingCardMinLevel = cfg.Bind<int>("CallingCard", "MinLevel", 1, "Minimum Syndicate level before blank calling cards appear in the Supplies catalog."); CallingCardPrice = cfg.Bind<int>("CallingCard", "Price", 25, "Price (credits) of one blank calling card in the Supplies app. Flat, not scaled by the gear price multiplier."); CallingCardMonikerMaxLength = cfg.Bind<int>("CallingCard", "MonikerMaxLength", 24, "Maximum length of a signed moniker in the cruncher editor (characters)."); CallingCardResidualFraction = cfg.Bind<float>("CallingCard", "ResidualFraction", 0.15f, "How much residual 'heat' leaving a signed card adds when the scene resolves cold, a small self-clamping bump (≈ +3% of the meter). A signed claim is a taunt the investigators keep on file; the more you leave, the more the trail warms. 0 = no residual add."); CallingCardSecondUnmaskHeat = cfg.Bind<int>("CallingCard", "SecondUnmaskHeat", 25, "Flat PlayerHeatScore added the SECOND time (and after) a carded moniker of yours is unmasked: the press now has a dossier on your hand. Between the masked-description bump (12) and a positive ID (40)."); CallingCardReauthorFee = cfg.Bind<int>("CallingCard", "ReauthorFee", 1000, "Cost (credits) to voluntarily re-author your moniker to a NEW name after the first free signing. A voluntary re-author does NOT burn the old name: it stays reusable (pay the fee again to return to it); only getting CAUGHT burns a name. Re-authoring is blocked while any signed card is still out at an open scene. 0 = free re-authoring."); CallingCardRaidRevealEnabled = cfg.Bind<bool>("CallingCard", "RaidRevealEnabled", true, "During a warrant home-raid, enforcers connect you to any calling-card persona whose moniker is on a card found at your home: the current moniker on your unused stash cards, and the baked moniker on any scene card you carried back home. Every printed murder signed with a FOUND moniker is pinned to you (murder fine, warrant extends, that moniker burned); murders you signed under a DIFFERENT moniker (no matching card at home) stay anonymous. Cards you carry, stash elsewhere, or left at the scene are safe: the sweep only reads cards physically at the raided home."); RepairEchelonFloors = cfg.Bind<bool>("Echelons", "RepairEchelonFloors", true, "Repair high-security 'echelon' floors on city load. A new game LOADS a pre-generated city whose floors deserialize with isEchelons stuck false (a vanilla save/load bug: NewFloor.Load trusts a stale serialized flag instead of recomputing it), so echelon floors never trespass-flag you and cameras/enforcers ignore you there even without the echelon-access perk. This recomputes the flag per floor from the building preset exactly as city-generation does, restoring trespassing + camera/enforcer detection. Idempotent; a no-op on correctly-serialized cities; self-heals existing saves. Disable to restore vanilla (buggy) behaviour."); CrippleEnabled = cfg.Bind<bool>("Cripple", "Enabled", true, "Non-lethal player melee/gunshot hits 'cripple' an NPC: they move slower (limp) for a while. Refresh-only (repeat hits reset the timer, never stack). Session-only: a mid-effect save/load forgets it (it's a short combat debuff)."); CrippleSpeedMultiplier = cfg.Bind<float>("Cripple", "SpeedMultiplier", 0.3f, "Crippled NPCs move at this fraction of normal speed (0.3 = 70% slower). Clamped 0.05-1."); CrippleDurationSeconds = cfg.Bind<float>("Cripple", "DurationSeconds", 90f, "How long (REAL seconds) a cripple lasts before the NPC recovers full speed."); CrippleBloodTrailEnabled = cfg.Bind<bool>("Cripple", "BloodTrailEnabled", true, "#D: a crippled NPC leaves a light blood drip as they limp (pure flair: no health loss, no aggro; the drops use the game's own blood spatter and fade on the normal decal timer). Off = no trail."); CamerasIdentify = cfg.Bind<bool>("Cameras", "Identify", true, "Security cameras that see you commit a murder, OR see you INSIDE the crime-scene apartment, IDENTIFY you: a guaranteed warrant, beaten only by wearing the mask. A camera that only catches you in the common AREA of the scene's floor (hall/landing) does NOT identify you: it just raises heat (see NearSceneHeat), since it never saw the murder or placed you at the body. Cameras act as eyewitnesses that can't be knocked out. Cameras inside your OWN apartment are exempt."); CameraNearSceneHeat = cfg.Bind<int>("Cameras", "NearSceneHeat", 15, "Heat added when a common-area camera (hall/landing on the crime-scene floor) catches you UNMASKED near, but not inside, a crime scene. It does NOT identify you (no warrant); it only raises suspicion. The camera can't report a murder it hasn't found, so, like a witness, it RECORDS the sighting at the time (you get a cue) and the heat only lands once the body is discovered and the footage is reviewed (about WitnessReportMinHours-MaxHours later). Once per scene. A camera inside the scene apartment, or one that saw the kill, still identifies you outright."); CameraNearSceneFreshWindowHours = cfg.Bind<int>("Cameras", "NearSceneFreshWindowHours", 2, "How many in-game HOURS after a kill a common-area camera sighting still raises suspicion (see NearSceneHeat). Past this window, a camera that catches you in the hall/landing near the scene no longer adds heat: a casual sighting has gone cold. Only affects the SUSPICION path; a camera that saw the murder or caught you INSIDE the scene still identifies you outright, regardless of time. 0 = no window (a sighting counts for as long as the case is open, the previous behaviour)."); CameraSweepEnabled = cfg.Bind<bool>("Cameras", "SweepEnabled", true, "When a camera spots a WANTED or MASKED player, dispatch a brief enforcer sweep to WHERE THEY WERE SPOTTED, separate from the crime-scene response and the home raid. ONE sweep at a time: a new one is sent only after the previous finishes, so a hunted player is chased with natural delay instead of spamming the city's ~12 enforcers."); CamerasRespectDarkness = cfg.Bind<bool>("Cameras", "RespectDarkness", true, "Cameras use the game's OWN light/stealth model: your personal light level (plus crouch-stealth; running or a lit FLASHLIGHT gives you away) sets the maximum distance a camera can detect you, exactly like vanilla camera sighting. Standing in the dark defeats a camera unless you're nearly under it (point-blank ~0.75m always spots you). Applies to EVERY Syndicate camera check: kill-witness ID, at-scene ID, near-scene suspicion heat, the wanted-player camera hunt, and the unmask face-connect. Human witnesses are unaffected. It can only ever SHRINK detection in darkness (a provable no-op in bright light). Disable to restore the previous behaviour (cameras ignore darkness)."); HudClusterEnabled = cfg.Bind<bool>("Hud", "ClusterEnabled", true, "Show the Syndicate HUD cluster along the top of the screen: your Syndicate level and open crime scenes (top-left), a MASK ON/OFF indicator (top-right, when the mask disk is installed), and the heat/evidence meter (top-centre). Turn off to hide the whole cluster."); HudSceneTableEnabled = cfg.Bind<bool>("Hud", "SceneTableEnabled", true, "Show a small table under the Syndicate-level badge listing your open crime scenes (victim + status: undiscovered / under investigation / going cold). Auto-hides when you have no open scenes. Requires the HUD cluster to be on."); HuntMode = cfg.Bind<HuntMode>("Enforcers", "HuntMode", TheSyndicate.Heat.HuntMode.EnforcerPursuit, "How enforcers become hostile while you're wanted. EnforcerPursuit (recommended): only enforcers who actually SEE you chase you, no civilian panic. GlobalIllegal: vanilla 'wanted', the whole city reacts (may cause a hitch when the hunt starts)."); PursuitResponseRange = cfg.Bind<float>("Enforcers", "PursuitResponseRange", 5f, "SetPersue 'response range' when an enforcer acquires you: how far additional enforcers get recruited. Keep low to avoid pathfinding spikes."); PursuitMaxDistanceMeters = cfg.Bind<float>("Enforcers", "PursuitMaxDistanceMeters", 50f, "EnforcerPursuit mode: only consider enforcers this close (or in your building) for the sight check each tick."); PursuitRequireLineOfSight = cfg.Bind<bool>("Enforcers", "PursuitRequireLineOfSight", true, "Require a physical line-of-sight ray (no wall in between) before an enforcer or witness counts as seeing you. Disable to restore the vanilla awareness-only check."); RequirePlayerInsideScene = cfg.Bind<bool>("Manhunt", "RequirePlayerInsideScene", true, "Only count 'caught red-handed' / scene witnesses when the PLAYER is physically inside the crime scene's apartment (any room), not the lobby/stairs/landing/another floor."); BodyDiscoveryRequireLineOfSight = cfg.Bind<bool>("Manhunt", "BodyDiscoveryRequireLineOfSight", true, "Require a physical line-of-sight ray before an NPC in a DIFFERENT room can spot a dead/KO'd body (vanilla sees through walls within ~6.5m). Same-room spotting and body smell are unaffected: a hidden body is still found by anyone who walks in or eventually smells it. Disable to restore vanilla through-wall spotting."); BatchReportScopeEnabled = cfg.Bind<bool>("Manhunt", "BatchReportScopeEnabled", true, "When a citizen calls the enforcers after finding ONE body, the base game reports EVERY undiscovered body in the WHOLE city at once (crediting that one caller, and dispatching enforcers plus raising an alarm lockdown at each). With the Syndicate quietening your kills they can pile up, so a single call would flush all of them at the same instant across the map. This limits one call to the body actually found plus any at the caller's own location, so your other hidden kills stay unfound until someone finds THEM. Only your own tracked kills are scoped; bodies from unrelated crimes keep the base-game city-wide behaviour. Disable to restore the base game."); WelfareRaidEnabled = cfg.Bind<bool>("Manhunt", "WelfareRaidEnabled", true, "When the welfare window on an undiscovered body lapses, dispatch an enforcer welfare check to the victim's home (a concerned relative / missed-work report). The cops finding the body IS the discovery. Disabled: the scene silently flips to discovered as before."); WelfareRaidDelayHours = cfg.Bind<float>("Manhunt", "WelfareRaidDelayHours", 0.25f, "In-GAME hours between the welfare call coming in and enforcers setting out (CallEnforcers' delay is game-hours). 0.25h = 15 game-minutes."); WelfareRaidForceCrimeScene = cfg.Bind<bool>("Manhunt", "WelfareRaidForceCrimeScene", false, "If true the welfare response is dispatched pre-flagged as a crime scene (tape + guard). Leave false: the game flags it automatically the moment responders find a dead occupant, and a false alarm (body moved) then stays low-key."); MurderFineAmount = cfg.Bind<int>("Enforcers", "MurderFineAmount", 2000, "Credits added to the Syndicate fine ledger per Identified murder. Collected on capture (leaving a minimum), remainder persists forever; settleable early at a discount."); IllegalRefreshSeconds = cfg.Bind<float>("Enforcers", "IllegalRefreshSeconds", 30f, "Seconds the player's 'illegal' hunt flag lasts per refresh while warranted (re-applied at half this cadence). Lower = it lapses faster if the mod ever stops ticking."); StakeoutEnabled = cfg.Bind<bool>("Enforcers", "StakeoutEnabled", true, "On a warrant, send enforcers to the suspect's home. If the suspect is there, enforcer-pursuit engages; if not, the native response guards/patrols it."); HomeRaidDelayHours = cfg.Bind<float>("Enforcers", "HomeRaidDelayHours", 0.05f, "In-GAME hours before enforcers set out for the suspect's home after a warrant (CallEnforcers' delay is game-hours, not seconds). 0.05h ~ 3 game-minutes; set 0 for instant."); StakeoutGuardHours = cfg.Bind<float>("Enforcers", "StakeoutGuardHours", 24f, "Game-hours per guard-duty shift at the suspect's home (mirrors the game's own 24h guard goal). The guard is automatically re-posted while the warrant lasts."); StakeoutForceCrimeScene = cfg.Bind<bool>("Enforcers", "StakeoutForceCrimeScene", false, "If true the home raid is dispatched as a crime scene (tapes + a lingering guard, like a murder scene); if false it's a plain targeted response. Probe both to see which posts a guard."); RepPenaltyOnIdentifiedContract = cfg.Bind<int>("Enforcers", "RepPenaltyOnIdentifiedContract", 25, "Underworld rep the Syndicate docks when you're Identified for killing one of THEIR contract targets (once per murder). Botching a sanctioned job embarrasses them. 0 disables."); RepPenaltyOnIdentifiedRandom = cfg.Bind<int>("Enforcers", "RepPenaltyOnIdentifiedRandom", 10, "Underworld rep docked when you're Identified for a NON-contract (freelance/random) kill, less than a botched contract, but still sloppy. 0 disables."); HitmanLevelPenaltyOnIdentified = cfg.Bind<int>("Enforcers", "HitmanLevelPenaltyOnIdentified", 0, "Reserved: hitman-level knock on being Identified (tier already derives from rep). 0 = off."); RepMessageDelaySeconds = cfg.Bind<float>("Enforcers", "RepMessageDelaySeconds", 20f, "Seconds to delay the 'Syndicate standing has taken a hit' toast so it doesn't flood the screen during the caught/wanted flurry."); FineMinimumBalance = cfg.Bind<int>("Fines", "FineMinimumBalance", 5, "Credits always left in your pocket when murder fines are collected at hospital (vanilla leaves 5)."); FineEarlyPayoffPercent = cfg.Bind<int>("Fines", "FineEarlyPayoffPercent", 60, "Call the Dispatcher to settle your outstanding murder fines EARLY (before capture) for this percent of the total. Capture always collects 100%."); RaidWeaponSeizureEnabled = cfg.Bind<bool>("Fines", "RaidWeaponSeizureEnabled", true, "During the warrant home-raid, if a murder weapon you used is at your apartment, enforcers seize it (it's destroyed) and surcharge that murder's fine, even if you're not home."); RaidWeaponSurchargePercent = cfg.Bind<int>("Fines", "RaidWeaponSurchargePercent", 50, "Home-raid weapon-seizure surcharge, per seized weapon per victim, as a percent of MurderFineAmount (50 = 1000cr at the 2000 default). 0 = seize with no fine bump."); CarryWeaponSeizureEnabled = cfg.Bind<bool>("Fines", "CarryWeaponSeizureEnabled", true, "If you're busted (downed while wanted) while carrying a murder weapon you used, it's confiscated (destroyed) and that murder's fine is surcharged."); CarryWeaponSurchargePercent = cfg.Bind<int>("Fines", "CarryWeaponSurchargePercent", 50, "Caught-carrying weapon surcharge, per confiscated weapon per victim, as a percent of MurderFineAmount (50 = 1000cr at the 2000 default). 0 = confiscate with no fine bump."); CruncherRaidPenaltyEnabled = cfg.Bind<bool>("Fines", "CruncherRaidPenaltyEnabled", true, "During the warrant home-raid, if a computer at your home is powered on AND logged in as you, enforcers pull your files: the murder fine is surcharged and the Syndicate docks your standing. Power off or log out before you run."); CruncherRaidSurchargePercent = cfg.Bind<int>("Fines", "CruncherRaidSurchargePercent", 25, "Cruncher-exposure surcharge as a percent of MurderFineAmount (25 = 500cr at the 2000 default). 0 = no fine bump."); CruncherRaidRepLoss = cfg.Bind<int>("Fines", "CruncherRaidRepLoss", 5, "Underworld rep docked when the raid finds your cruncher powered on and logged in. 0 disables."); SafehouseBreachEnabled = cfg.Bind<bool>("Enforcers", "SafehouseBreachEnabled", true, "During a warrant, when hunters see you flee into your own (locked) home, dispatch ONE enforcer breach+sweep so they don't just give up at your door. If they don't find you they leave; the landing guard stays posted. Fleeing home again triggers another breach."); RepPenaltyOnContractCancel = cfg.Bind<int>("Contracts", "RepPenaltyOnContractCancel", 5, "Underworld rep the Syndicate docks when you cancel/abandon an active contract. Backing out has a price, but a small one. 0 disables."); ContractBuildingSpreadEnabled = cfg.Bind<bool>("Contracts", "BuildingSpreadEnabled", true, "Soft-diversify a freshly generated offer batch across the city: successive picks are nudged away from a building/floor already used by another offer or an active contract, so targets don't all cluster in one apartment block. A SOFT preference (a scoring penalty, not a hard ban): an unrelated later refresh may still reuse a building. Off = the previous uniform-random pick."); ContractSpreadSameBuildingPenalty = cfg.Bind<int>("Contracts", "SpreadSameBuildingPenalty", 3, "Building-spread scoring penalty when a candidate lives in a building already used this batch (different floor). Higher = stronger diversification. Compared against the tier-distance score (0-2 range), so 3 strongly discourages a repeat without hard-banning it."); ContractSpreadSameFloorPenalty = cfg.Bind<int>("Contracts", "SpreadSameFloorPenalty", 5, "Building-spread scoring penalty when a candidate lives on the SAME building AND floor already used this batch. Higher than the same-building penalty (a shared floor is the worst clustering). "); CaseBoardEnabled = cfg.Bind<bool>("Contracts", "CaseBoardEnabled", true, "Give each accepted hit contract a custom case on the detective pinboard (target + objective checklist). Purely presentation: turning it off changes nothing about payouts or objectives; the contract still works entirely through the Cruncher and v-mail."); AutoCollectHours = cfg.Bind<int>("Contracts", "AutoCollectHours", 24, "In-game hours after a contract's payment becomes collectable (its crime scene resolves) before the fixer auto-wires it if you never collect at your terminal, a safety net so payment can never get stranded. Clamped to at least 1."); TipLineEnabled = cfg.Bind<bool>("TipLine", "Enabled", true, "Enable the Syndicate anonymous tip line, an in-world phone number you can dial to 'call in' a hidden contract body, so the investigation (and your forensics-gated payment) starts sooner instead of waiting for the welfare check. It's accelerate-only: leaving prints/witnesses still gets you caught, just sooner. When off, no tip line is registered and the case-board hint / v-mail line are omitted."); TipLineNumber = cfg.Bind<int>("TipLine", "Number", 9008477, "The tip line's phone number (7 digits, shown as XXX-XXXX). Default 9008477 = '900-TIPS'. Keep it outside BOTH the game's real-number band (1000000-6999000) AND its dynamic fake-callback band (8000000-8999000, which murder-ransom and side-job 'call this number' objectives draw from), and away from the reserved service numbers (1540000/5410000/9110000). Only change it if a save shows a collision."); PauseLineEnabled = cfg.Bind<bool>("PauseLine", "Enabled", true, "Enable the Dispatcher line, a phone number (in your phonebook from the start) you dial to go off the books: no new auto-assigned contracts, no board refills, no accepts, until you dial again. Active contracts are unaffected. If disabled while paused, contracts auto-resume on next load."); PauseLineNumber = cfg.Bind<int>("PauseLine", "Number", 9007378, "The Dispatcher's number (7 digits, shown as XXX-XXXX). Default 9007378 = '900-REST'. Keep it outside the game's real-number band (1000000-6999000), its dynamic fake-callback band (8000000-8999000), away from the reserved service numbers (1540000/5410000/9110000), and different from the tip line. Only change it if a save shows a collision."); AutoAssignMaxLevel = cfg.Bind<int>("Contracts", "AutoAssignMaxLevel", 2, "Highest Syndicate level at which the fixer AUTO-ASSIGNS contracts (one at a time, via v-mail). From the next level up you pick your own jobs on the home Cruncher's job board."); MaxConcurrentContractsL3 = cfg.Bind<int>("Contracts", "MaxConcurrentContractsL3", 3, "How many contracts you may hold active at once once the job board unlocks (Level 3). Below that it is always one at a time. Accept jobs from the board up to this cap; a value of 1 restores the old single-contract behaviour at every level."); OfferBoardSize = cfg.Bind<int>("Contracts", "OfferBoardSize", 8, "How many contract offers the L3 Cruncher job board holds (1-8). The app pages the list (store/status/refresh rows stay pinned, roughly 3 offers per page), so larger boards just add pages."); RefreshBoardCooldownHours = cfg.Bind<int>("Contracts", "RefreshBoardCooldownHours", 6, "In-game hours between manual board REFRESHes (the L3 Cruncher's REFRESH row rerolls the whole board for free). The cooldown stops reroll-fishing for a specific objective. 0 = no cooldown."); OfferLifetimeHours = cfg.Bind<int>("Contracts", "OfferLifetimeHours", 48, "In-game hours an un-accepted listing stays on the L3 Cruncher job board before the market moves on and it is replaced (evaluated when the board renders). Each listing varies by a few hours so the board turns over gradually. Very low values churn the board every open."); ContractCooldownHours = cfg.Bind<int>("Contracts", "ContractCooldownHours", 12, "In-game hours after a contract closes (completed/failed/cancelled) before the next auto-assigned job arrives."); FirstContractDelayHours = cfg.Bind<int>("Contracts", "FirstContractDelayHours", 1, "In-game hours after becoming eligible (activating with the Dispatcher, or reaching a contractable level with onboarding off) before the first auto-assigned contract v-mail lands. NOTE: onboarding uses [Onboarding] FirstJobAfterActivationHours for the post-check-in first job instead."); DeadlineHours = cfg.Bind<int>("Contracts", "DeadlineHours", 48, "In-game hours the optional deadline objective allows from the moment the contract is issued (values below 24 are clamped: the client always gives you at least a day to investigate and plan)."); CloseToastEnabled = cfg.Bind<bool>("Contracts", "CloseToastEnabled", true, "Feature 4: after a contract closes, a short toast tells you when the next job lands (and, pre-L3, that you can call the Dispatcher to pull one now)."); OnboardingEnabled = cfg.Bind<bool>("Onboarding", "Enabled", true, "The v1.0 onboarding flow: an intro v-mail on a new game, then contracts stay GATED until you call the Dispatcher to check in (with 30/60-min reminders), a gentle first 'proving' hit, and one-shot tutorial toasts. Turn OFF to start already activated with no intro/tutorial (contracts flow immediately once you reach Level 1)."); FirstJobAfterActivationHours = cfg.Bind<int>("Onboarding", "FirstJobAfterActivationHours", 2, "In-game hours after you call the Dispatcher to check in before the FIRST contract lands, time to learn the streets. 0 = immediate."); CheckInReminder1Minutes = cfg.Bind<int>("Onboarding", "CheckInReminder1Minutes", 30, "In-game minutes after the intro v-mail before the first 'call the Dispatcher' reminder toast. 0 disables it."); CheckInReminder2Minutes = cfg.Bind<int>("Onboarding", "CheckInReminder2Minutes", 60, "In-game minutes after the intro v-mail before the SECOND and final check-in reminder. After this, reminders stop. Calling in is your choice. 0 disables it."); MoleEmailEnabled = cfg.Bind<bool>("Heat", "MoleEmailEnabled", true, "Feature 5: when a case goes cold but you left forensic traces, a v-mail from the Syndicate's source inside the Enforcers itemizes exactly what fed the residual heat (fingerprints by room, a camera that clocked you, a beaten body)."); ClearTrespassOnResolve = cfg.Bind<bool>("Heat", "ClearTrespassOnResolve", true, "#120: when a kill in a PUBLIC area draws a welfare-raid/camera enforcer response, the game flags that location a crime scene (which makes a normally-public room read as TRESPASSING for you). Because the mod resolves its own kills outside the vanilla case, that flag can otherwise stick forever. When on, resolving the scene clears the flag and lets the enforcer call wind down cleanly. Off = leave the vanilla flag (it may persist)."); ContractBasePayout = cfg.Bind<int>("ContractEconomy", "ContractBasePayout", 900, "BASE of the payout formula: payout = BASE x difficulty x info x (1 + sum of met objective bonuses)."); DiffMultT2 = cfg.Bind<float>("ContractEconomy", "DiffMultT2", 1.7f, "Difficulty multiplier for Tier 2 contracts (Tier 1 is fixed at 1.0)."); DiffMultT3 = cfg.Bind<float>("ContractEconomy", "DiffMultT3", 2.8f, "Difficulty multiplier for Tier 3 contracts."); InfoMultPartial = cfg.Bind<float>("ContractEconomy", "InfoMultPartial", 1.5f, "Payout multiplier when the offer only carries a PARTIAL lead (you investigate the rest; Full is fixed at 1.0)."); InfoMultCold = cfg.Bind<float>("ContractEconomy", "InfoMultCold", 2.2f, "Payout multiplier for a COLD lead (a thin scrap: initial, description, district)."); InfoFullPctT1 = cfg.Bind<int>("ContractEconomy", "InfoFullPctT1", 60, "Tier-1 info distribution: percent of offers with a FULL dossier. Partial below; Cold = 100 - Full - Partial."); InfoPartialPctT1 = cfg.Bind<int>("ContractEconomy", "InfoPartialPctT1", 40, "Tier-1 info distribution: percent with a PARTIAL lead."); InfoFullPctT2 = cfg.Bind<int>("ContractEconomy", "InfoFullPctT2", 40, "Tier-2 info distribution: percent FULL."); InfoPartialPctT2 = cfg.Bind<int>("ContractEconomy", "InfoPartialPctT2", 45, "Tier-2 info distribution: percent PARTIAL (Cold = remainder, 15 by default)."); InfoFullPctT3 = cfg.Bind<int>("ContractEconomy", "InfoFullPctT3", 15, "Tier-3 info distribution: percent FULL."); InfoPartialPctT3 = cfg.Bind<int>("ContractEconomy", "InfoPartialPctT3", 50, "Tier-3 info distribution: percent PARTIAL (Cold = remainder, 35 by default)."); InvestigationBias = cfg.Bind<int>("ContractEconomy", "InvestigationBias", 0, "Single global shifter (-50..50): positive moves offer chance from Full toward Partial/Cold (more detective work), negative toward Full (more point-and-click)."); ObjChanceZero = cfg.Bind<int>("ContractEconomy", "ObjChanceZero", 45, "Percent of contracts rolled with ZERO optional objectives."); ObjChanceOne = cfg.Bind<int>("ContractEconomy", "ObjChanceOne", 40, "Percent with exactly ONE optional objective (two = the remainder, 15 by default)."); BonusClean = cfg.Bind<float>("ContractEconomy", "BonusClean", 0.25f, "Payout fraction added for the clean/no-ID objective, paid in ESCROW when the victim's case closes without identifying you."); BonusDeadline = cfg.Bind<float>("ContractEconomy", "BonusDeadline", 0.2f, "Payout fraction added for completing within the deadline."); BonusPhoto = cfg.Bind<float>("ContractEconomy", "BonusPhoto", 0.3f, "Payout fraction added for the photo-of-body objective, earned by photographing the corpse any time after the kill (before you collect)."); BonusSuicide = cfg.Bind<float>("ContractEconomy", "BonusSuicide", 0.5f, "Payout fraction for the forge-suicide objective, paid in ESCROW when the victim's forged-overdose case closes WITHOUT identifying you (a blown forgery pays nothing and brings heat). Needs the vial (#48), which is mailed on accept."); BonusPoison = cfg.Bind<float>("ContractEconomy", "BonusPoison", 0.35f, "Payout fraction for the poison-kill objective, earned by killing the target with the Syndicate syringe (#48), which is mailed to you on accept. Any other kill method misses it."); EnablePhotoObjective = cfg.Bind<bool>("ContractEconomy", "EnablePhotoObjective", true, "Allow the photo-of-body bonus objective to roll (landed in slice 3). Photograph the corpse with the camera any time after the kill and before you collect to earn the bonus; missing it just drops that bonus."); EnablePoisonObjective = cfg.Bind<bool>("ContractEconomy", "EnablePoisonObjective", true, "Allow the poison-kill objective to roll (#48). The target must be killed with the mailed syringe. Mutually exclusive with the forge-suicide objective on a given contract."); EnableSuicideObjective = cfg.Bind<bool>("ContractEconomy", "EnableSuicideObjective", true, "Allow the forge-suicide objective to roll (#48). Stage a clean overdose with the mailed vial; the bonus escrows and pays only if the case closes without your name on it. Mutually exclusive with the poison-kill objective on a given contract."); EnableDisposalObjective = cfg.Bind<bool>("ContractEconomy", "EnableDisposalObjective", true, "Allow the weapon-disposal-at-sea TERM to attach to a contract (#73). The Syndicate mails you a specific poison/melee weapon; you must kill the target with THAT weapon and then throw it in the sea. Keep the weapon and the base fee takes a cut (DisposalPenalty). Not a bonus, a penalty-bearing term."); DisposalContractPercent = cfg.Bind<int>("ContractEconomy", "DisposalContractPercent", 25, "Percent of eligible contracts (poison/melee-killable, not already a poison-kill/forge-suicide job) that carry the sea-disposal term. 0 disables it without touching EnableDisposalObjective."); DisposalPenalty = cfg.Bind<float>("ContractEconomy", "DisposalPenalty", 0.45f, "Fraction of the BASE fee DEDUCTED when you don't dump the issued weapon at sea (you keep the weapon instead). 0.45 = a ~45% base cut. Clamped so the payout never goes negative."); DisposalMinLevel = cfg.Bind<int>("ContractEconomy", "DisposalMinLevel", 0, "Lowest Syndicate level at which the sea-disposal term attaches to a poison/forge job (0 = from the very start, so a Level-0 proving hit that mails you a syringe also asks you to dump it)."); DisposalWeaponList = cfg.Bind<string>("ContractEconomy", "DisposalWeaponList", "Syringe,KitchenKnifeLarge,CombatKnife,Truncheon,BasBouleBat,VintageSword,Katana", "PascalCase item presets the Syndicate may issue for a sea-disposal job, POISON or MELEE only, never firearms. Comma-separated. One is picked at random per contract, mailed on accept."); EnableFallForge = cfg.Bind<bool>("ContractEconomy", "EnableFallForge", true, "Allow the forge-suicide objective to roll its 'forge a fall' variant (drag a KO'd target off a high point: stairwell opening, rooftop) instead of the overdose variant (#75). While off, every forge-suicide job is the overdose variant."); FallVariantPercent = cfg.Bind<int>("ContractEconomy", "FallVariantPercent", 40, "When EnableFallForge is on, percent of forge-suicide jobs that use the 'forge a fall' variant (the rest are 'forge an overdose')."); FallKillsEnabled = cfg.Bind<bool>("Fall", "FallKillsEnabled", true, "Enable fall-damage kills (#75): drag a KO'd (still-living) citizen off a high point (a stairwell opening, a rooftop) and the fall kills them (the base game has no NPC fall damage). A clean case reads as an accidental fall. Off = dropped bodies just land, as in vanilla."); LethalFallHeight = cfg.Bind<float>("Fall", "LethalFallHeight", 6f, "How far (in metres) a dragged KO'd body must fall below its release point to die from the impact. ~6m is roughly two floors, so a KO'd body dropped from a second-floor window dies. Lower = easier fall kills."); HeaveKey = cfg.Bind<KeyCode>("Fall", "HeaveKey", (KeyCode)103, "While DRAGGING a KO'd body, press this to HEAVE it in the direction you're looking (the vanilla drag can't lift a body up and out a window well). Drag the body to the opening on the ground, aim out, and heave. None = disabled."); HeaveSpeed = cfg.Bind<float>("Fall", "HeaveSpeed", 6f, "Launch speed (metres/second) of a heaved body. Higher = it flies further/faster out the window. Tune to taste."); DragLiftAssist = cfg.Bind<float>("Fall", "DragLiftAssist", 0.8f, "0..1 lift assist while DRAGGING a KO'd body: cancels this fraction of gravity on the ragdoll so it feels lighter to lift up and out a window (the native drag can't be made mass-lighter; this counters gravity instead). 0 = vanilla weight, 1 = weightless while held (never > gravity, so it won't float away). Only applies while you're dragging; released/heaved bodies fall normally."); FallSceneAtLanding = cfg.Bind<bool>("Fall", "FallSceneAtLanding", true, "After a fall kill (#75/#83), re-anchor the crime scene to where the corpse comes to REST, so a fall down a deep shaft or off a rooftop reads at the landing instead of the mid-fall kill point. (The kill itself ALWAYS fires the instant a lethal fall is confirmed: a dead body is immune to the game's ragdoll-recovery teleport that otherwise makes a still-living thrown body vanish to a random spot.) Off = leave the scene at the kill point."); FallAntiSnagEnabled = cfg.Bind<bool>("Fall", "FallAntiSnagEnabled", true, "#J: a body heaved out a window sometimes snags on the building's outer wall mid-fall and hangs there instead of dropping to the street. With this on, a body that stalls in mid-air while falling (confirmed by a downward probe finding no ground beneath it) gets a short downward shove to knock it loose so it keeps falling. Collision is NEVER disabled (the body always lands normally, no risk of it dropping through the world), so the worst case is a harmless no-op. Off = vanilla fall physics."); FallAntiSnagForce = cfg.Bind<float>("Fall", "FallAntiSnagForce", 6f, "#J: downward shove speed (m/s) applied to a mid-air-snagged falling body to dislodge it. Higher = a firmer knock. Clamped to a small floor."); HomeVacateEnabled = cfg.Bind<bool>("Cleanup", "HomeVacateEnabled", true, "#83 crime-scene coherence: when a target is killed AWAY from home and their case resolves, the game only cleans the death location: their apartment is left frozen as if they still live there. With this on, the Syndicate quietly vacates the victim's home a while later (HomeVacateDelayHours) exactly as the base game does for an at-home death: their furniture is removed and the address is cleared, but only once you're not there to see it and no living resident remains. Off = leave the home untouched (vanilla behaviour for away kills)."); HomeVacateDelayHours = cfg.Bind<int>("Cleanup", "HomeVacateDelayHours", 48, "In-game hours after an away-from-home kill's case resolves before the victim's empty home is vacated (mirrors the base-game crime-scene cleanup delay). Only fires while you're elsewhere."); BodyCleanupEnabled = cfg.Bind<bool>("Cleanup", "BodyCleanupEnabled", true, "Leak fix: our quietened kills suppress the vanilla body-discovery, so the base game never removes the corpse: a permanent body keeps spawning blood footprints forever, which leaks memory badly over a long session. With this on, once a kill's case resolves the Syndicate hands the death location to the base game's own crime-scene cleanup, which removes the corpse a while later (BodyCleanupDelayHours) once you're not there to see it. Off = leave bodies forever (NOT recommended: this is the memory-leak fix)."); BodyCleanupDelayHours = cfg.Bind<int>("Cleanup", "BodyCleanupDelayHours", 3, "In-game hours after a kill's case resolves before the corpse is handed to the base-game cleanup for removal. The delay leaves the body in place long enough for a photo/objective; the base game then removes it once you're away from the scene."); KODurationMultiplier = cfg.Bind<float>("Combat", "KODurationMultiplier", 3f, "Multiplies how long a knocked-out NPC stays down, so you have time to drag + throw them (the base game's KO is short). Vanilla base is ~1 in-game hour, so 3 = ~3 in-game hours. Only affects ROLLED knockouts (a normal non-lethal takedown); the Syndicate's own timed KOs (poison holds, witness pins) are left exactly as set, and the PLAYER's own KO is never lengthened. 1 = vanilla. Higher = longer."); BluntKOMultiplier = cfg.Bind<float>("Combat", "BluntKOMultiplier", 1.6f, "#H: scales the per-hit damage of BLUNT weapons (truncheon, bat, etc.) when YOU swing them, so a knockout takes FEWER hits: blunt force stuns. Only affects the player's own attacks; NPC-carried blunt weapons are unchanged. 1 = vanilla. Higher = fewer hits to KO."); BladeKOMultiplier = cfg.Bind<float>("Combat", "BladeKOMultiplier", 0.6f, "#H: scales the per-hit damage of BLADED weapons (knife, katana, sword, etc.) when YOU swing them, so a NON-LETHAL knockout takes MORE hits: blades cut, they don't stun (a lethal cut still kills as before, this only changes the KO ramp). Only affects the player's own attacks. 1 = vanilla. Lower = more hits to KO."); Level1Rep = cfg.Bind<int>("Progression", "Level1Rep", 20, "Underworld rep needed to reach Syndicate Level 1 (first earned milestone; you start at Level 0)."); Level2Rep = cfg.Bind<int>("Progression", "Level2Rep", 50, "Underworld rep needed to reach Syndicate Level 2. Also the contract-difficulty Tier-2 boundary."); Level3Rep = cfg.Bind<int>("Progression", "Level3Rep", 100, "Underworld rep needed to reach Syndicate Level 3 (top). Also the contract-difficulty Tier-3 boundary."); Level1Reward = cfg.Bind<int>("Progression", "Level1Reward", 500, "Credits paid out the first time you reach Level 1. A random sync disk from RewardDiskPool is also mailed to your operations base."); Level2Reward = cfg.Bind<int>("Progression", "Level2Reward", 1500, "Credits paid out the first time you reach Level 2. A second random disk from RewardDiskPool, always different from the Level-1 one, is also mailed."); Level3Reward = cfg.Bind<int>("Progression", "Level3Reward", 4000, "Credits paid out the first time you reach Level 3. Also unlocks the Syndicate contract board on your home cruncher."); RewardDiskPool = cfg.Bind<string>("Progression", "RewardDiskPool", "ElGen-Vigor,BlackMarket-Infiltrator,BlackMarket-Trespasser,ElGen-Tenacity,ElGen-Physique", "Comma-separated SyncDiskPreset asset names the Level 1 and Level 2 reward disks are drawn from (one random disk each, the second always different from the first). Unknown names are skipped."); SkipCampaignEnabled = cfg.Bind<bool>("Progression", "SkipCampaignEnabled", true, "Cheat / accessibility: show a 'Skip the climb' option in the Dispatcher call that jumps you straight to Level 3, so every rank-gated feature (contract board, top-tier contracts, the full Supplies catalog) unlocks at once. It sends NO money, gear, or reward disks - only the rank itself, so you still earn your economy. One-way, and the option hides once you are Level 3. Set false to remove it from the Dispatcher menu entirely."); SocialCreditPerToken = cfg.Bind<int>("SocialCredit", "SocialCreditPerToken", 350, "Native social credit gained per standing purchase (1 hit token + money). Native level thresholds: L2=350, L3=750, L4=1250, L5=2000, L6/echelon=3200, L8/Normal-cap=6000. Default 350 = a hit meaningfully advances your standing."); SocialCreditPurchaseMoneyCost = cfg.Bind<int>("SocialCredit", "SocialCreditPurchaseMoneyCost", 500, "Credits charged alongside the hit token for each standing purchase (bribe/laundering flavor). Modest by design: the hit token is the real cost."); SuppliesEnabled = cfg.Bind<bool>("Supplies", "Enabled", true, "Enable the Syndicate Supply app on your home Cruncher: buy gear (weapons, tools, meds, the Second Skin disk) at a discount and have it dropped at your door in a courier case. When off, no icon, no deliveries."); SuppliesDiscountPercent = cfg.Bind<int>("Supplies", "DiscountPercent", 20, "Syndicate discount off an item's street value (its preset max value), as a percent. 20 = you pay 80% of the vanilla price. Clamped 0-90."); SyncDiskAppPrice = cfg.Bind<int>("Supplies", "SyncDiskAppPrice", 5000, "Flat price (credits) of the Second Skin (gloves) sync disk in the Supplies app. Bypasses the street-value multiplier and the discount, so it always costs exactly this. Default 5000. (The False Face mask has its own price: see MaskDiskAppPrice.)"); MaskDiskAppPrice = cfg.Bind<int>("Supplies", "MaskDiskAppPrice", 3500, "Flat price (credits) of the False Face (mask) sync disk in the Supplies app. Bypasses the street-value multiplier and the discount. Default 3500."); RefundLegacyDiskDuds = cfg.Bind<bool>("Supplies", "RefundLegacyDiskDuds", true, "On load, refund the disk price for any pre-0.9.5 APP-BOUGHT sync disk that reloaded as an unusable dud (its identity is unrecoverable: mask and gloves are the same keyless world object, so it can't be restored) and remove the dud, so you're made whole and can re-order. Off = leave the unusable dud in place, no refund. (Free level-reward duds are still restored to their correct disk regardless.)"); SuppliesDeliveryHours = cfg.Bind<int>("Supplies", "DeliveryHours", 1, "In-game hours from checkout to the courier's knock at your door. Clamped to at least 0 (0 = essentially immediate on the next tick). Shared by the gear and groceries apps."); GroceriesEnabled = cfg.Bind<bool>("Supplies", "GroceriesEnabled", true, "Enable the bonus Syndicate Groceries app on your home Cruncher: buy food/drink delivered in a courier case, same as gear. When off, no groceries icon."); GroceriesMarkupPercent = cfg.Bind<int>("Supplies", "GroceriesMarkupPercent", 10, "Delivery markup added to a grocery item's shop price, as a percent (simulates store delivery fees). Default 10 = you pay 110% of the item's value. Clamped 0-500."); GearPriceMultiplier = cfg.Bind<float>("Supplies", "GearPriceMultiplier", 3f, "GLOBAL price multiplier on the GEAR bucket: everything that isn't a firearm or ammo (blades, blunt, poison, tools, consumables, the custom sync disks). Applied to the base game item value at load, so EVERY vendor charges more, not just the Syndicate shop (which then still takes DiscountPercent off). See GunPriceMultiplier / AmmoPriceMultiplier for those buckets. 1.0 = vanilla. Groceries (incl. medical) are unaffected. Clamped 0.1-100."); AmmoPriceMultiplier = cfg.Bind<float>("Supplies", "AmmoPriceMultiplier", 3f, "GLOBAL price multiplier on AMMUNITION (pistol/rifle/shotgun rounds). Same global mechanism as GearPriceMultiplier, scoped to ammo. 1.0 = vanilla. Clamped 0.1-100."); GunPriceMultiplier = cfg.Bind<float>("Supplies", "GunPriceMultiplier", 7f, "GLOBAL price multiplier on FIREARMS (sidearms + long guns). Same global mechanism as GearPriceMultiplier, scoped to guns: guns are the premium tier. 1.0 = vanilla. Clamped 0.1-100."); FirearmsEnabled = cfg.Bind<bool>("Firearms", "Enabled", true, "Enable player weapon-firing: aim a held gun and shoot (left-click, or the fallback FireKey). Off = guns behave like vanilla (a melee club at most)."); FirearmsHeadshotLethal = cfg.Bind<bool>("Firearms", "HeadshotLethal", true, "A headshot with ANY gun kills outright (the precise-shot / silenced-pistol payoff). Off = headshots do normal damage like body shots."); FirearmsHeadshotRadius = cfg.Bind<float>("Firearms", "HeadshotRadius", 0.35f, "How close (metres) a bullet's impact must land to the head anchor to count as a headshot. Raise if headshots aren't registering, lower if body shots are wrongly counting as headshots."); FirearmsHeadshotDebug = cfg.Bind<bool>("Firearms", "HeadshotDebug", false, "Log a diagnostic line for each PELLET that hits a citizen (collider name, distance to the head anchor, headshot result) so headshot detection can be calibrated. Fires per-pellet per-shot, so leave OFF in normal play (heavy console logging can aggravate the Windows-console freeze). Turn on only when tuning headshots."); FirearmsOneShotWeapons = cfg.Bind<string>("Firearms", "OneShotWeapons", "Shotgun,SniperRifle", "Comma-separated PascalCase gun preset names whose BODY shot kills outright. Others rely on the two-hit rule (a shot KOs; a second shot on the KO'd target executes), same as melee. Blank = no gun one-shots on a body hit."); FirearmsAccuracy = cfg.Bind<float>("Firearms", "Accuracy", 1f, "0-1 aim precision fed to the game's Shoot(). 1.0 = the bullet goes exactly where the crosshair points (and makes lethality detection exact). Below 1.0 adds spread to the DAMAGE ray only; lethality still follows the crosshair."); FirearmsMinCooldownSeconds = cfg.Bind<float>("Firearms", "MinFireCooldownSeconds", 0.4f, "Floor (real seconds) under the gun's native per-weapon fire cadence, so nothing fires faster than this. Raise to slow all guns down."); FirearmsConsumeAmmo = cfg.Bind<bool>("Firearms", "ConsumeAmmo", true, "Require and consume one matching ammo item per shot (the gun's own ammunition list decides the match). Off = unlimited free firing (debug/sandbox)."); FirearmsSpawnCasings = cfg.Bind<bool>("Firearms", "SpawnCasings", true, "Leave a spent shell casing at the scene per shot (revolvers/no-eject guns leave none). If you're NOT wearing gloves, the casing carries YOUR fingerprint, feeding the forensics/ID system. Off = no casing, no print trail."); FirearmsFireKey = cfg.Bind<KeyCode>("Firearms", "FireKey", (KeyCode)0, "Optional dedicated fire key that fires WITHOUT holding aim. In the normal model you hold right-mouse to aim, then left-click to fire; bind a key here if you want a one-press fire that ignores aim (accessibility). None = aim-and-click only."); FirearmsMeleeKey = cfg.Bind<KeyCode>("Firearms", "MeleeKey", (KeyCode)0, "Optional explicit melee key, REDUNDANT now that left-click (without holding aim) already melees. Kept as an alias for muscle memory. None = LMB-alone melee only."); FirearmsRecoil = cfg.Bind<float>("Firearms", "Recoil", 15f, "Camera recoil kick applied per shot (a JoltCamera amplitude). 0 = no recoil. The game uses ~6 for a mild jolt and 45-60 for a violent one, so 15 is a firm-but-controllable kick. Raise for heavier recoil."); FirearmsPoseLab = cfg.Bind<bool>("Firearms", "PoseLab", false, "DEBUG ONLY: enable the first-person Pose Lab hotkeys for surveying/adjusting how a held item is posed: works on any firearm AND the syringe (see the log for the keymap when one is equipped). Leave OFF in normal play."); FirearmsReloadKey = cfg.Bind<KeyCode>("Firearms", "ReloadKey", (KeyCode)114, "Key to reload the held gun (tops the magazine up from ammo boxes in your inventory; takes ReloadSeconds). None = no reload key: a gun becomes UNUSABLE once its starting magazine and any reserve are spent, so keep a key bound (or set ConsumeAmmo=false)."); FirearmsReloadSeconds = cfg.Bind<float>("Firearms", "ReloadSeconds", 2.5f, "Seconds a reload takes. Switching or holstering the gun mid-reload cancels it with no ammo consumed. Clamped to a 0.2s floor."); FirearmsMagazineSizes = cfg.Bind<string>("Firearms", "MagazineSizes", "SemiAutomaticPistol:12,SemiAutomaticPistolSilenced:12,Revolver:6,Shotgun:6,BattleRifle:30,SniperRifle:5", "Per-gun magazine capacity (PascalCase preset name:rounds, comma-separated). Rounds loaded per gun are tracked in the save; an unlisted gun defaults to 8."); FirearmsAmmoBoxSizes = cfg.Bind<string>("Firearms", "AmmoBoxSizes", "21AmmoSmall:12,21AmmoLarge:50,309Ammo:20,BuckshotAmmo:6,HuntingAmmo:6", "Rounds contained in each ammo BOX item (preset name:rounds). A reload consumes whole boxes and banks any excess as loose rounds in that gun-group's reserve. Unlisted box defaults to 12."); FirearmsAmmoHud = cfg.Bind<bool>("Firearms", "AmmoHud", true, "Show a small corner readout of magazine/reserve while a gun is drawn (e.g. '7 / 12 · 38 rounds'). Off = no on-screen ammo counter (reload/empty toasts still fire)."); FirearmsAutoFireWeapons = cfg.Bind<string>("Firearms", "AutoFireWeapons", "BattleRifle", "Comma-separated PascalCase gun preset names that fire full-auto while you HOLD left-click. All others are semi (one shot per click). Default = the Faucon (BattleRifle) only."); FirearmsAutoFireInterval = cfg.Bind<float>("Firearms", "AutoFireIntervalSeconds", 0.12f, "Seconds between shots for an auto weapon while the trigger is held (~0.12 = 500 rpm). Overrides the per-weapon cadence for auto guns only. Clamped to a 0.05s floor."); FirearmsAutoRecoilScale = cfg.Bind<float>("Firearms", "AutoRecoilScale", 0.4f, "Multiplier on the per-shot recoil kick for auto weapons (full Recoil per shot at 8 shots/sec would be unplayable). 1.0 = same kick as a semi shot."); SniperZoomEnabled = cfg.Bind<bool>("Firearms", "SniperZoomEnabled", true, "Enable the sniper scope: while a sniper is held, HOLD right-mouse to zoom (scroll to vary). Off = the sniper aims like any other gun (RMB just enables firing, no zoom)."); SniperZoomWeapons = cfg.Bind<string>("Firearms", "SniperZoomWeapons", "SniperRifle", "Comma-separated PascalCase gun preset names that get the variable scope zoom while aiming. Default = the Hamilton (SniperRifle) only; every other gun treats RMB purely as the fire modifier."); SniperZoomDefault = cfg.Bind<float>("Firearms", "SniperZoomDefault", 4f, "Starting scope magnification the first time you aim a given sniper (each gun then remembers its own level). Clamped between Min and Max."); SniperZoomMin = cfg.Bind<float>("Firearms", "SniperZoomMin", 2f, "Minimum scope magnification (scroll down to reach it). 1 = no zoom."); SniperZoomMax = cfg.Bind<float>("Firearms", "SniperZoomMax", 8f, "Maximum scope magnification (scroll up to reach it)."); SniperZoomStepPercent = cfg.Bind<int>("Firearms", "SniperZoomStepPercent", 15, "Percent the magnification changes per scroll notch. Clamped 1-100."); SniperZoomEaseSpeed = cfg.Bind<float>("Firearms", "SniperZoomEaseSpeed", 12f, "How fast the scope eases toward the target magnification (magnification units per second). Higher = snappier zoom-in."); SniperZoomScalesSensitivity = cfg.Bind<bool>("Firearms", "SniperZoomScalesSensitivity", true, "Scale mouse look-sensitivity down with zoom so a scoped view isn't twitchy. Off = full sensitivity while scoped."); SniperScopedRecoilScale = cfg.Bind<float>("Firearms", "SniperScopedRecoilScale", 0.35f, "Multiplier on the per-shot recoil kick while SCOPED (a full kick at 8x is nauseating). 1 = same kick as unscoped; 0 = no kick while scoped."); SniperZoomReadout = cfg.Bind<bool>("Firearms", "SniperZoomReadout", true, "Show a small magnification readout (e.g. 4.0x) beside the crosshair while a sniper scope is zoomed. Scroll to change zoom. Off = no readout."); FirearmsShotgunPellets = cfg.Bind<int>("Firearms", "ShotgunPellets", 6, "Pellets fired per shotgun shell (uses the weapon's own 'shots' value if higher). Each pellet is an independent damage ray; the first flies straight to the crosshair, the rest spread."); FirearmsShotgunSpread = cfg.Bind<float>("Firearms", "ShotgunSpread", 0.06f, "Shotgun pellet spread in the game's aim-jitter units (~0.06 ≈ a 4-5 degree cone). 0 = no spread (all pellets on the crosshair). Higher = wider pattern, thinner at range."); FirearmsShotgunLethalPellets = cfg.Bind<int>("Firearms", "ShotgunLethalPellets", 3, "How many pellets must strike one citizen for a lethal (one-shot) shotgun hit. Point-blank the whole pattern lands (>= this) and kills; at range the pattern thins and only wounds."); FirearmsShotgunAmmoKey = cfg.Bind<KeyCode>("Firearms", "ShotgunAmmoSwitchKey", (KeyCode)98, "Key to switch the shotgun's loaded shell between BUCKSHOT (spreads) and DEER SLUG (a single ball, no spread, a heavy one-shot). The switch is immediate and shows on the ammo HUD; any shells of the other type still chambered are ejected, so reload to fill the new type. None = the shotgun just uses whichever shell you have (buckshot preferred)."); FirearmsDrawnDread = cfg.Bind<bool>("Firearms", "DrawnGunDread", true, "Citizens NEAR you while you hold a drawn gun lose nerve (fear), an ambient proximity effect (not tied to precise aim). Off = a drawn gun is not itself intimidating. Enforcers on duty and armed NPCs resist natively."); FirearmsDrawnNerve = cfg.Bind<float>("Firearms", "DrawnGunNerve", -0.05f, "Nerve drained per ~0.5s from each nearby citizen while your gun is drawn (negative = fear; citizen nerve maxes ~0.5, so -0.05 rattles them within a couple of seconds). Nerve regenerates when you leave, so it's a soft pressure. More negative = faster panic; 0 = off."); FirearmsDrawnRange = cfg.Bind<float>("Firearms", "DrawnGunRange", 8f, "Range (metres) within your current room where citizens feel the drawn-gun dread."); EnforcersUnfazedByDrawnGuns = cfg.Bind<bool>("Firearms", "EnforcersUnfazedByDrawnGuns", true, "Enforcers (on OR off duty) no longer lose nerve from SEEING your drawn firearm. The base game drains an onlooker's nerve while any weapon is out: on-duty enforcers are natively immune, but OFF-duty ones take the full hit and can cower or flee from a raised gun. Trained officers shouldn't spook. Firearms in the player's hands only; civilians, melee weapons, and every other fear source are untouched. Off = vanilla."); FirearmsBulletRangeFloor = cfg.Bind<float>("Firearms", "BulletRangeFloor", 500f, "Minimum shot range (metres) for ANY firearm: floors each weapon's native maximumBulletRange, which is short because guns are melee items in the base game (this is what capped the sniper). Never lowers a longer native range; the bullet ray still stops at the first wall/object, so it only matters when you actually have line of sight. Raise for even longer sniping; lower toward realism."); FirearmsGunshotsAlarm = cfg.Bind<bool>("Firearms", "GunshotsAlarm", true, "Gunshots alarm citizens who HEAR them: nerve drop + they investigate/flee and remember a gunshot. Off = firing makes noise but no one reacts to hearing it. (The silenced pistol is exempt by default.)"); FirearmsAlarmExemptGuns = cfg.Bind<string>("Firearms", "AlarmExemptGuns", "SemiAutomaticPistolSilenced", "Comma-separated PascalCase gun preset names whose gunshots do NOT alarm hearers (silenced weapons). Their fire sound is left vanilla. Blank = every gun alarms."); FirearmsGunshotSpook = cfg.Bind<float>("Firearms", "GunshotSpook", 0.2f, "Minimum nerve drop applied to a citizen who hears a gunshot (raises the gun sound's spook value if lower). Higher = louder panic from hearing shots."); FirearmsRoomTurnOnHit = cfg.Bind<bool>("Firearms", "RoomTurnOnHit", true, "A non-lethal gunshot HIT rouses the victim AND everyone in their room to pursue you (the native Shoot doesn't alert the surroundings, so this is on us). Off = only the wounded victim reacts."); FirearmsRoomTurnRange = cfg.Bind<float>("Firearms", "RoomTurnRange", 10f, "Range (metres) within the victim's room whose occupants turn on you after a non-lethal hit. Only used when RoomTurnOnHit is on."); FirearmsInteractYieldDistance = cfg.Bind<float>("Firearms", "InteractYieldDistance", 2f, "How close (metres) an interactable under the crosshair must be for a trigger-pull to open/use it INSTEAD of firing. The game registers doors/items you're looking at from across a room; this gate means only a door/item within reach eats the shot, so you can fire at a distant door. Raise if the gun still refuses to fire near doors; lower toward the game's ~1.8m interact reach for stricter behaviour."); MaskToggleKey = cfg.Bind<KeyCode>("Mask", "ToggleKey", (KeyCode)110, "Key to pull the 'False Face' mask on/off (only works once the mask sync-disk is installed). While worn, witnesses to your kills can only describe a masked figure (a Suspicious scene, not a warrant in your name), but a covered face unnerves civilians near you. None = no toggle key (use the debug button)."); MaskScareEnabled = cfg.Bind<bool>("Mask", "ScareCivilians", true, "While the mask is worn, nearby civilians lose nerve (fear), a proximity effect so masking has a cost, mirroring the drawn-gun dread. Off = the mask is socially invisible."); MaskScareNerve = cfg.Bind<float>("Mask", "ScareNerve", -0.02f, "Nerve drained per ~0.5s from each nearby civilian while the mask is worn (negative = fear; citizen nerve maxes ~0.5, so -0.02 rattles them over ~12s of proximity rather than a few seconds). Nerve regenerates when you leave. More negative = faster panic; 0 = off."); MaskScareRange = cfg.Bind<float>("Mask", "ScareRange", 8f, "Range (metres) within your current room where civilians feel the masked-figure dread."); MaskScareEnforcers = cfg.Bind<bool>("Mask", "ScareEnforcers", false, "Whether the worn-mask scare also unnerves ENFORCERS (like a masked suspect should). Default false: mirrors the drawn-gun dread, which exempts enforcers, and avoids provoking unpredictable enforcer AI. On = a masked figure spooks everyone."); MaskedDescriptionHeat = cfg.Bind<int>("Mask", "MaskedDescriptionHeat", 12, "Heat added (once per scene) when a masked witness reports a 'masked figure', noticeably more than a bare body-discovery (+8) but far below a positive ID (+40). Tunes how much a described-but-unnamed sighting stings."); MaskUnmaskInViewIdentifies = cfg.Bind<bool>("Mask", "UnmaskInViewIdentifies", true, "F5: taking the mask OFF while a witness or camera