Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of FactionTactics Client v1.0.13
FactionTactics.Client.dll
Decompiled 3 hours agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Logging; using FactionTactics.Ambience; using FactionTactics.Combat; using FactionTactics.ConsoleCmds; using FactionTactics.Doctrine; using FactionTactics.Orders; using FactionTactics.Util; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("FactionTactics.Client")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.13.0")] [assembly: AssemblyInformationalVersion("1.0.13+b72dd217344d7e9cda3ad9c8d0c8ee1bd7ac5931")] [assembly: AssemblyProduct("FactionTactics.Client")] [assembly: AssemblyTitle("FactionTactics.Client")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.13.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 FactionTactics.ConsoleCmds { public static class FtConfigRpc { public const string RpcCmd = "FT_ConfigCmd"; public const string RpcReply = "FT_ConfigReply"; public const string RpcSync = "FT_ConfigSync"; private static bool _registered; private static object? _registeredOn; public static Action<string>? LocalPrint { get; set; } public static bool IsRegistered => _registered; public static void EnsureRegistered() { try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && (!_registered || _registeredOn != instance)) { instance.Register<ZPackage>("FT_ConfigCmd", (Action<long, ZPackage>)OnCmd); instance.Register<ZPackage>("FT_ConfigReply", (Action<long, ZPackage>)OnReply); instance.Register<ZPackage>("FT_ConfigSync", (Action<long, ZPackage>)OnSync); _registered = true; _registeredOn = instance; FactionTacticsLog.Debug("[FT] FtConfigRpc registered FT_ConfigCmd/FT_ConfigReply/FT_ConfigSync"); } } catch (Exception ex) { FactionTacticsLog.Debug("FtConfigRpc.EnsureRegistered: " + ex.GetType().Name + ": " + ex.Message); } } public static void SendCmd(string op, string? key = null, string? value = null) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown EnsureRegistered(); if (ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null) { PushLocal("[ft] not connected"); return; } try { ZPackage val = new ZPackage(); val.Write(op ?? ""); val.Write(key ?? ""); val.Write(value ?? ""); byte[] array = val.GetArray(); try { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null && serverPeer.m_uid != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(serverPeer.m_uid, "FT_ConfigCmd", new object[1] { (object)new ZPackage(array) }); return; } } catch { } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FT_ConfigCmd", new object[1] { (object)new ZPackage(array) }); } catch (Exception ex) { PushLocal("[ft] RPC send failed: " + ex.Message); } } private static void OnCmd(long senderPeerId, ZPackage pkg) { if (pkg != null && !((Object)(object)ZNet.instance == (Object)null)) { ZNet.instance.IsServer(); } } private static void Reply(long peerId, string text) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown try { EnsureRegistered(); if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(text ?? ""); if (peerId != 0L) { ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "FT_ConfigReply", new object[1] { val }); } } } catch (Exception ex) { FactionTacticsLog.Debug("FtConfigRpc.Reply: " + ex.GetType().Name + ": " + ex.Message); } } private static void BroadcastSync() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown try { EnsureRegistered(); if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(CombatTuning.HoldAttackCooldown); val.Write(CombatTuning.HoldAttackRangeFactor); val.Write(CombatTuning.SwingStaggerMs); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FT_ConfigSync", new object[1] { val }); } } catch (Exception ex) { FactionTacticsLog.Debug("FtConfigRpc.BroadcastSync: " + ex.GetType().Name + ": " + ex.Message); } } private static void OnReply(long senderPeerId, ZPackage pkg) { if (pkg == null) { return; } try { string[] array = (pkg.ReadString() ?? "").Split(new char[1] { '\n' }, StringSplitOptions.None); for (int i = 0; i < array.Length; i++) { PushLocal(array[i]); } } catch (Exception ex) { FactionTacticsLog.Debug("FtConfigRpc.OnReply: " + ex.GetType().Name + ": " + ex.Message); } } private static void OnSync(long senderPeerId, ZPackage pkg) { if (pkg == null) { return; } try { CombatTuning.HoldAttackCooldown = pkg.ReadSingle(); CombatTuning.HoldAttackRangeFactor = pkg.ReadSingle(); CombatTuning.SwingStaggerMs = pkg.ReadSingle(); } catch (Exception ex) { FactionTacticsLog.Debug("FtConfigRpc.OnSync: " + ex.GetType().Name + ": " + ex.Message); } } private static void PushLocal(string line) { try { LocalPrint?.Invoke(line); } catch { } try { FactionTacticsLog.Debug(line); } catch { } } public static bool PeerIsAdmin(long peerId) { if ((Object)(object)ZNet.instance == (Object)null) { return false; } try { if (peerId == 0L || peerId == ZNet.GetUID()) { try { if (ZNet.instance.LocalPlayerIsAdminOrHost()) { return true; } } catch { } } } catch { } try { MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "PlayerIsAdmin", new Type[1] { typeof(long) }, (Type[])null); if (methodInfo != null) { return (bool)methodInfo.Invoke(ZNet.instance, new object[1] { peerId }); } } catch { } try { MethodInfo methodInfo2 = AccessTools.Method(typeof(ZNet), "IsAdmin", (Type[])null, (Type[])null); if (methodInfo2 != null) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(long)) { return (bool)methodInfo2.Invoke(ZNet.instance, new object[1] { peerId }); } } } catch { } try { ZNetPeer peer = ZNet.instance.GetPeer(peerId); if (peer == null) { return false; } string text = null; try { ISocket socket = peer.m_socket; text = ((socket != null) ? socket.GetHostName() : null); } catch { } FieldInfo fieldInfo = AccessTools.Field(typeof(ZNet), "m_adminList"); if (fieldInfo != null && !string.IsNullOrEmpty(text)) { object value = fieldInfo.GetValue(ZNet.instance); if (value != null) { MethodInfo methodInfo3 = AccessTools.Method(value.GetType(), "Contains", (Type[])null, (Type[])null) ?? AccessTools.Method(value.GetType(), "IsInList", (Type[])null, (Type[])null); if (methodInfo3 != null) { return (bool)methodInfo3.Invoke(value, new object[1] { text }); } } } } catch (Exception ex) { FactionTacticsLog.Debug("PeerIsAdmin fallback: " + ex.GetType().Name + ": " + ex.Message); } return false; } public static bool LocalPlayerIsAdmin() { try { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.LocalPlayerIsAdminOrHost()) { return true; } } catch { } try { return PeerIsAdmin(ZNet.GetUID()); } catch { return false; } } } } namespace FactionTactics.Ambience { public static class DeathRushAudio { private static readonly Dictionary<int, float> LastPulseAt = new Dictionary<int, float>(); private const float DefaultCooldown = 4.5f; public static void PulseIfNeeded(MonsterAI ai, MemberIntent intent) { //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ai == (Object)null || intent == null || !intent.DeathRush || intent.OrderKind != DoctrineOrderKind.Charge) { return; } int instanceID; try { instanceID = ((Object)ai).GetInstanceID(); } catch { return; } float time = Time.time; float num = 4.5f; if (num < 0.5f) { num = 0.5f; } if (LastPulseAt.TryGetValue(instanceID, out var value) && time - value < num) { return; } LastPulseAt[instanceID] = time; try { ((BaseAI)ai).Alert(); } catch (Exception ex) { FactionTacticsLog.Debug("DeathRushAudio.Alert failed: " + ex.GetType().Name + ": " + ex.Message); } try { EffectList value2 = Traverse.Create((object)ai).Field("m_alertedEffects").GetValue<EffectList>(); if (value2 != null && value2.HasEffects()) { value2.Create(((Component)ai).transform.position, ((Component)ai).transform.rotation, (Transform)null, 1f, -1, default(ZDOID)); } } catch (Exception ex2) { FactionTacticsLog.Debug("DeathRushAudio.m_alertedEffects failed: " + ex2.GetType().Name + ": " + ex2.Message); } } } } namespace FactionTactics.Combat { public static class CombatAuthority { public static bool ShouldReleaseToVanillaUpdateAI(MemberIntent? intent) { return ShouldReleaseToVanilla(intent); } public static bool ShouldReleaseToVanilla(MemberIntent? intent) { return intent?.AllowVanillaChase ?? true; } public static bool ShouldSoleBrain(MemberIntent? intent) { if (intent != null) { return !ShouldReleaseToVanilla(intent); } return false; } public static bool ShouldAllowVanillaMoveTo(MemberIntent? intent) { return ShouldReleaseToVanilla(intent); } public static bool IsAttackReleaseOrder(DoctrineOrderKind order) { return order == DoctrineOrderKind.Charge; } } public static class CombatDriver { private const float MoveArriveDist = 1.5f; private const float AdvanceStopDist = 2f; private const float DefaultMeleeRange = 2.5f; private const float HoldGroundPinDist = 2.5f; private static readonly ConcurrentDictionary<long, float> NextSwingTime = new ConcurrentDictionary<long, float>(); public static long DriveCount { get; private set; } public static long AttackAttempts { get; private set; } public static long Swings { get; private set; } public static long HoldBlocks { get; private set; } public static void ResetCounters() { DriveCount = (AttackAttempts = (Swings = (HoldBlocks = 0L))); NextSwingTime.Clear(); } public static bool ShouldReleaseToVanilla(MemberIntent intent) { return CombatAuthority.ShouldReleaseToVanilla(intent); } public static void Drive(MonsterAI ai, MemberIntent intent, float dt) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) DriveCount++; PulseDeathRushAudio(ai, intent); CallUpdateTarget(ai, dt); bool flag = IsLineFront(intent); if (intent.HoldGround) { if (!HoldGroundDestinationFar(ai, intent)) { ((BaseAI)ai).StopMoving(); FaceThreatOrSlot(ai, intent); SoftSuppressHunt(ai); TryDriveAttack(ai, intent); return; } intent.HoldGround = false; } Vector3 val = intent.DesiredPosition; if (val == Vector3.zero && intent.AllowVanillaChase) { Character targetCreature = ((BaseAI)ai).GetTargetCreature(); if ((Object)(object)targetCreature != (Object)null) { val = ((Component)targetCreature).transform.position; } } if (flag && !intent.AllowVanillaChase) { CallMoveTo((BaseAI)(object)ai, dt, val, 1.5f, intent.PreferRun); Vector3 position = ((Component)ai).transform.position; float num = position.x - val.x; float num2 = position.z - val.z; if ((float)Math.Sqrt(num * num + num2 * num2) < 2f) { ((BaseAI)ai).StopMoving(); } FaceThreatOrSlot(ai, intent); SoftSuppressHunt(ai); TryDriveAttack(ai, intent); return; } if (val != Vector3.zero) { CallMoveTo((BaseAI)(object)ai, dt, val, 1.5f, intent.PreferRun); } if (intent.PreferKeepRange && !intent.AllowVanillaChase) { Character targetCreature2 = ((BaseAI)ai).GetTargetCreature(); if ((Object)(object)targetCreature2 != (Object)null) { CallLookAt((BaseAI)(object)ai, ((Component)targetCreature2).transform.position); } } else if (intent.AllowVanillaChase) { FaceThreatOrSlot(ai, intent); } TryDriveAttack(ai, intent); } public static bool IsLineFront(MemberIntent intent) { if (intent.Role != SquadRole.Front && intent.Role != SquadRole.Leader) { return false; } if (intent.Formation != FormationType.ShieldWall && intent.Formation != FormationType.Line) { return false; } if (intent.OrderKind != DoctrineOrderKind.Hold && intent.OrderKind != DoctrineOrderKind.Advance) { return intent.OrderKind == DoctrineOrderKind.ProtectMissiles; } return true; } public static void SuppressVanillaChase(MonsterAI ai, MemberIntent intent) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (intent.AllowVanillaChase) { return; } try { Traverse obj = Traverse.Create((object)ai); obj.Field("m_targetCreature").SetValue((object)null); obj.Field("m_targetStatic").SetValue((object)null); Vector3 val = intent.DesiredPosition; if (val == Vector3.zero) { val = ((Component)ai).transform.position; } obj.Field("m_lastKnownTargetPos").SetValue((object)val); } catch (Exception ex) { FactionTacticsLog.Debug("SuppressVanillaChase Traverse failed: " + ex.GetType().Name + ": " + ex.Message); } SoftSuppressHunt(ai); } public static void SoftSuppressHunt(MonsterAI ai) { try { if (((BaseAI)ai).HuntPlayer()) { ((BaseAI)ai).SetHuntPlayer(false); } } catch { } } private static void FaceThreatOrSlot(MonsterAI ai, MemberIntent intent) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) try { Character targetCreature = ((BaseAI)ai).GetTargetCreature(); if ((Object)(object)targetCreature != (Object)null) { CallLookAt((BaseAI)(object)ai, ((Component)targetCreature).transform.position); return; } } catch { } if (intent.DesiredPosition != Vector3.zero) { CallLookAt((BaseAI)(object)ai, intent.DesiredPosition); } } private static void CallUpdateTarget(MonsterAI ai, float dt) { try { Character? character = ValheimIds.GetCharacter((BaseAI?)(object)ai); Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); if (val != null) { MethodInfo methodInfo = AccessTools.Method(typeof(MonsterAI), "UpdateTarget", (Type[])null, (Type[])null); if (!(methodInfo == null)) { object[] parameters = new object[4] { val, dt, false, false }; methodInfo.Invoke(ai, parameters); } } } catch (Exception) { } } private static void TryDriveAttack(MonsterAI ai, MemberIntent intent) { if (intent.OrderKind == DoctrineOrderKind.RetreatAndReform || (intent.OrderKind == DoctrineOrderKind.Kite && intent.Role != SquadRole.Missile)) { return; } bool flag = intent.HoldGround || (IsLineFront(intent) && (intent.OrderKind == DoctrineOrderKind.Hold || intent.OrderKind == DoctrineOrderKind.ProtectMissiles)); try { Character targetCreature = ((BaseAI)ai).GetTargetCreature(); if ((Object)(object)targetCreature == (Object)null) { if (flag) { HoldBlocks++; } return; } if (IsMidAttack(ai)) { if (flag) { HoldBlocks++; } return; } if (!IsInWeaponBand(ai, targetCreature, intent)) { if (flag) { HoldBlocks++; } return; } long key = InstanceId(ai); float time = Time.time; float num = EffectiveCooldown(ai, intent); if (NextSwingTime.TryGetValue(key, out var value) && time < value) { if (flag) { HoldBlocks++; } return; } AttackAttempts++; MethodInfo methodInfo = AccessTools.Method(typeof(MonsterAI), "DoAttack", (Type[])null, (Type[])null); if (!(methodInfo == null)) { methodInfo.Invoke(ai, new object[2] { targetCreature, false }); Swings++; NextSwingTime[key] = time + num; } } catch (Exception) { } } private static float EffectiveCooldown(MonsterAI ai, MemberIntent intent) { float num = CombatTuning.HoldAttackCooldown; try { float value = Traverse.Create((object)ai).Field("m_minAttackInterval").GetValue<float>(); if (value > num) { num = value; } } catch { } float num2 = CombatTuning.SwingStaggerMs * (float)Math.Max(0, intent.SlotIndex) / 1000f; if (intent.Role == SquadRole.Missile) { num2 += 0.05f; } return num + num2; } private static bool IsMidAttack(MonsterAI ai) { try { Character character = ValheimIds.GetCharacter((BaseAI?)(object)ai); if ((Object)(object)character != (Object)null) { MethodInfo methodInfo = AccessTools.Method(((object)character).GetType(), "InAttack", (Type[])null, (Type[])null); if (methodInfo != null && methodInfo.GetParameters().Length == 0 && (bool)methodInfo.Invoke(character, null)) { return true; } } } catch { } try { if (Traverse.Create((object)ai).Field("m_wasInAttack").GetValue<bool>()) { return true; } } catch { } return false; } private static bool IsInWeaponBand(MonsterAI ai, Character target, MemberIntent intent) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) try { Vector3 position = ((Component)ai).transform.position; Vector3 position2 = ((Component)target).transform.position; float num = position.x - position2.x; float num2 = position.z - position2.z; float num3 = (float)Math.Sqrt(num * num + num2 * num2); float num4 = 2.5f; float num5 = 0f; try { Traverse val = Traverse.Create((object)ai); num4 = val.Field("m_aiAttackRange").GetValue<float>(); if (num4 <= 0.01f) { num4 = 2.5f; } try { num5 = val.Field("m_aiAttackRangeMin").GetValue<float>(); } catch { num5 = 0f; } } catch { num4 = 2.5f; } try { AccessTools.Method(typeof(MonsterAI), "SelectBestAttack", (Type[])null, (Type[])null); } catch { } float num6 = CombatTuning.HoldAttackRangeFactor; if (num6 < 0.25f) { num6 = 0.25f; } num4 *= num6; if ((intent.PreferKeepRange || intent.Role == SquadRole.Missile) && num4 < 8f) { num4 = 12f; } if (num3 > num4) { return false; } if (num5 > 0.1f && num3 < num5 * 0.5f && intent.PreferKeepRange) { return false; } return true; } catch { return true; } } private static bool HoldGroundDestinationFar(MonsterAI ai, MemberIntent intent) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) Vector3 desiredPosition = intent.DesiredPosition; if (desiredPosition == Vector3.zero || (Object)(object)ai == (Object)null) { return false; } Vector3 position = ((Component)ai).transform.position; float num = position.x - desiredPosition.x; float num2 = position.z - desiredPosition.z; return num * num + num2 * num2 > 6.25f; } private static long InstanceId(MonsterAI ai) { try { long num = ValheimIds.FromCharacter(ValheimIds.GetCharacter((BaseAI?)(object)ai)); if (num != 0L) { return num; } } catch { } return ((Object)(object)ai != (Object)null) ? ((Object)ai).GetInstanceID() : 0; } private static bool CallMoveTo(BaseAI ai, float dt, Vector3 point, float dist, bool run) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) return Traverse.Create((object)ai).Method("MoveTo", new object[4] { dt, point, dist, run }).GetValue<bool>(); } private static void CallLookAt(BaseAI ai, Vector3 point) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) Traverse.Create((object)ai).Method("LookAt", new object[1] { point }).GetValue(); } private static void PulseDeathRushAudio(MonsterAI ai, MemberIntent intent) { if (intent != null && intent.DeathRush) { DeathRushAudio.PulseIfNeeded(ai, intent); } } } public static class CombatTuning { public static float HoldAttackCooldown = 0.85f; public static float HoldAttackRangeFactor = 1f; public static float SwingStaggerMs = 75f; public static void ApplyKnob(string key, string value) { if (string.IsNullOrWhiteSpace(key) || value == null) { return; } key = key.Trim(); if (float.TryParse(value.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { if (Matches(key, "HoldAttackCooldown")) { HoldAttackCooldown = Clamp(result, 0.05f, 10f); } else if (Matches(key, "HoldAttackRangeFactor")) { HoldAttackRangeFactor = Clamp(result, 0.25f, 3f); } else if (Matches(key, "SwingStaggerMs")) { SwingStaggerMs = Clamp(result, 0f, 2000f); } } } public static void CopyFromPluginConfig() { } private static bool Matches(string key, string name) { return string.Equals(key, name, StringComparison.OrdinalIgnoreCase); } private static float Clamp(float v, float lo, float hi) { if (!(v < lo)) { if (!(v > hi)) { return v; } return hi; } return lo; } } } namespace FactionTactics.Util { public static class FactionTacticsLog { public static Action<string>? DebugSink { get; set; } public static void Debug(string message) { try { DebugSink?.Invoke(message); } catch { } } } public static class ValheimIds { public static long ToLong(ZDOID zdoid) { return ((((ZDOID)(ref zdoid)).UserID & 0xFFFFFFFFu) << 32) | ((ZDOID)(ref zdoid)).ID; } public static long FromCharacter(Character? ch) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ch == (Object)null) { return 0L; } return ToLong(ch.GetZDOID()); } public static Character? GetCharacter(BaseAI? ai) { if ((Object)(object)ai == (Object)null) { return null; } Character value = Traverse.Create((object)ai).Field("m_character").GetValue<Character>(); if ((Object)(object)value != (Object)null) { return value; } return ((Component)ai).GetComponent<Character>(); } } } namespace FactionTactics.Doctrine { public enum DoctrineOrderKind { Hold, Advance, Charge, Flank, FocusFire, ProtectMissiles, RetreatAndReform, Kite } public enum SquadRole { Unassigned, Front, Missile, Flanker, Leader } } namespace FactionTactics.Orders { public static class IntentZdoCodec { [Flags] public enum IntentFlags { None = 0, Active = 1, HoldGround = 2, PreferRun = 4, AllowVanillaChase = 8, PreferKeepRange = 0x10, AssaultWallBreaker = 0x20, AssaultMissileCover = 0x40, AllowVanillaStructure = 0x80, DeathRush = 0x100 } public const int SchemaVersion = 3; public const float StaleAfterSeconds = 6f; public static IntentFlags PackFlags(MemberIntent intent) { IntentFlags intentFlags = IntentFlags.Active; if (intent.HoldGround) { intentFlags |= IntentFlags.HoldGround; } if (intent.PreferRun) { intentFlags |= IntentFlags.PreferRun; } if (intent.AllowVanillaChase) { intentFlags |= IntentFlags.AllowVanillaChase; } if (intent.PreferKeepRange) { intentFlags |= IntentFlags.PreferKeepRange; } if (intent.AssaultWallBreaker) { intentFlags |= IntentFlags.AssaultWallBreaker; } if (intent.AssaultMissileCover) { intentFlags |= IntentFlags.AssaultMissileCover; } if (intent.AllowVanillaStructure) { intentFlags |= IntentFlags.AllowVanillaStructure; } if (intent.DeathRush) { intentFlags |= IntentFlags.DeathRush; } return intentFlags; } public static void ApplyFlags(MemberIntent intent, IntentFlags flags) { intent.HoldGround = (flags & IntentFlags.HoldGround) != 0; intent.PreferRun = (flags & IntentFlags.PreferRun) != 0; intent.AllowVanillaChase = (flags & IntentFlags.AllowVanillaChase) != 0; intent.PreferKeepRange = (flags & IntentFlags.PreferKeepRange) != 0; intent.AssaultWallBreaker = (flags & IntentFlags.AssaultWallBreaker) != 0; intent.AssaultMissileCover = (flags & IntentFlags.AssaultMissileCover) != 0; intent.AllowVanillaStructure = (flags & IntentFlags.AllowVanillaStructure) != 0; intent.DeathRush = (flags & IntentFlags.DeathRush) != 0; } public static bool IsActive(IntentFlags flags) { return (flags & IntentFlags.Active) != 0; } public static bool IsFresh(float writtenAtSeconds, float nowSeconds, float staleAfter = 6f) { if (writtenAtSeconds <= 0f) { return false; } float num = nowSeconds - writtenAtSeconds; if (num < -2f) { return true; } if (num >= 0f) { return num <= staleAfter; } return false; } } public static class IntentZdoSync { public const string KeyVersion = "ft_iv"; public const string KeyFlags = "ft_flg"; public const string KeyOrder = "ft_ord"; public const string KeyFormation = "ft_frm"; public const string KeyStance = "ft_stn"; public const string KeyRole = "ft_rol"; public const string KeySlot = "ft_slot"; public const string KeyFocus = "ft_focus"; public const string KeyTime = "ft_t"; public const string KeySquadHash = "ft_sq"; public const string KeySlotIndex = "ft_sidx"; private static bool _loggedSchemaMismatch; public static long Writes { get; private set; } public static long SchemaWrites { get; private set; } public static long ReadsOk { get; private set; } public static long ReadsMiss { get; private set; } public static long ReadsStale { get; private set; } public static long SchemaMismatches { get; private set; } public static void ResetCounters() { Writes = (SchemaWrites = (ReadsOk = (ReadsMiss = (ReadsStale = (SchemaMismatches = 0L))))); _loggedSchemaMismatch = false; } public static int StableHash(string? s) { if (string.IsNullOrEmpty(s)) { return 0; } int num = 5381; foreach (char c in s) { num = ((num << 5) + num) ^ c; } return num; } public static bool TryGetZdo(MonsterAI? ai, out ZDO zdo) { zdo = null; if ((Object)(object)ai == (Object)null) { return false; } try { ZNetView component = ((Component)ai).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return false; } ZDO zDO = component.GetZDO(); if (zDO == null) { return false; } zdo = zDO; return true; } catch { return false; } } public static bool IsNetOwner(MonsterAI? ai) { if ((Object)(object)ai == (Object)null) { return false; } try { ZNetView component = ((Component)ai).GetComponent<ZNetView>(); return (Object)(object)component != (Object)null && component.IsValid() && component.IsOwner(); } catch { return false; } } public static bool Write(ZDO zdo, MemberIntent intent, float nowSeconds) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (zdo == null || intent == null) { return false; } try { zdo.Set("ft_iv", 3); zdo.Set("ft_flg", (int)IntentZdoCodec.PackFlags(intent)); zdo.Set("ft_ord", (int)intent.OrderKind); zdo.Set("ft_frm", (int)intent.Formation); zdo.Set("ft_stn", (int)intent.Stance); zdo.Set("ft_rol", (int)intent.Role); zdo.Set("ft_slot", intent.DesiredPosition); zdo.Set("ft_focus", intent.FocusTargetId.GetValueOrDefault()); zdo.Set("ft_t", nowSeconds); zdo.Set("ft_sq", StableHash(intent.SquadId)); zdo.Set("ft_sidx", intent.SlotIndex); Writes++; SchemaWrites++; return true; } catch (Exception ex) { FactionTacticsLog.Debug("IntentZdoSync.Write failed: " + ex.GetType().Name + ": " + ex.Message); return false; } } public static bool WriteFromMonsterAI(MonsterAI ai, MemberIntent intent, float nowSeconds) { if (TryGetZdo(ai, out ZDO zdo)) { return Write(zdo, intent, nowSeconds); } return false; } public static bool TryRead(ZDO zdo, float nowSeconds, out MemberIntent intent) { //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) intent = null; if (zdo == null) { ReadsMiss++; return false; } try { int num = zdo.GetInt("ft_iv", 0); switch (num) { case 0: ReadsMiss++; return false; default: SchemaMismatches++; ReadsMiss++; if (!_loggedSchemaMismatch) { _loggedSchemaMismatch = true; FactionTacticsLog.Debug(string.Format("[FT] INTENT SCHEMA MISMATCH: ZDO ft_iv={0} local={1} product={2}. ", num, 3, "1.0.13") + "Update FactionTactics server+client packs to the same 1.0.x release."); try { Debug.LogError((object)($"[FactionTactics] Intent schema mismatch (zdo={num} local={3}). " + "Client/server packs must match — reinstall both from the same 1.0.x release. Note: missing ZDO fields (zdo=0) with working RPC is OK in 1.0.3+.")); } catch { } } return false; case 3: { IntentZdoCodec.IntentFlags flags = (IntentZdoCodec.IntentFlags)zdo.GetInt("ft_flg", 0); if (!IntentZdoCodec.IsActive(flags)) { ReadsMiss++; return false; } if (!IntentZdoCodec.IsFresh(zdo.GetFloat("ft_t", 0f), nowSeconds)) { ReadsStale++; return false; } long num2 = zdo.GetLong("ft_focus", 0L); intent = new MemberIntent { OrderKind = (DoctrineOrderKind)zdo.GetInt("ft_ord", 0), Formation = (FormationType)zdo.GetInt("ft_frm", 0), Stance = (StanceType)zdo.GetInt("ft_stn", 0), Role = (SquadRole)zdo.GetInt("ft_rol", 0), DesiredPosition = zdo.GetVec3("ft_slot", Vector3.zero), FocusTargetId = ((num2 != 0L) ? new long?(num2) : ((long?)null)), SquadId = "", SlotIndex = zdo.GetInt("ft_sidx", 0) }; IntentZdoCodec.ApplyFlags(intent, flags); ReadsOk++; return true; } } } catch (Exception ex) { FactionTacticsLog.Debug("IntentZdoSync.TryRead failed: " + ex.GetType().Name + ": " + ex.Message); ReadsMiss++; return false; } } public static bool TryReadFromMonsterAI(MonsterAI ai, float nowSeconds, out MemberIntent intent) { intent = null; if (TryGetZdo(ai, out ZDO zdo)) { return TryRead(zdo, nowSeconds, out intent); } return false; } public static void Clear(ZDO zdo) { if (zdo == null) { return; } try { zdo.Set("ft_flg", 0); zdo.Set("ft_iv", 0); } catch { } } } public static class IntentRpcSync { private struct PendingEntry { public MemberIntent Intent; public float WrittenAt; } private struct CacheEntry { public MemberIntent Intent; public float WrittenAt; } public const string RpcName = "FT_MemberIntents"; private static readonly ConcurrentDictionary<long, PendingEntry> Pending = new ConcurrentDictionary<long, PendingEntry>(); private static readonly ConcurrentDictionary<long, CacheEntry> Cache = new ConcurrentDictionary<long, CacheEntry>(); private static bool _registered; private static object? _registeredOn; private static bool _loggedFirstApply; private static bool _loggedFirstPacket; private static bool _loggedRegister; public static long RpcIntentsSent { get; private set; } public static long RpcBatchesSent { get; private set; } public static long RpcPeerInvokes { get; private set; } public static long RpcIntentsReceived { get; private set; } public static long RpcPacketsReceived { get; private set; } public static long RpcCacheHits { get; private set; } public static long RpcSchemaMismatches { get; private set; } public static bool IsRegistered => _registered; public static void ResetCounters() { RpcIntentsSent = (RpcBatchesSent = (RpcPeerInvokes = 0L)); RpcIntentsReceived = (RpcPacketsReceived = (RpcCacheHits = (RpcSchemaMismatches = 0L))); _loggedFirstApply = false; _loggedFirstPacket = false; Pending.Clear(); Cache.Clear(); } public static void Queue(long zdoidPacked, MemberIntent intent, float nowSeconds) { if (zdoidPacked != 0L && intent != null) { Pending[zdoidPacked] = new PendingEntry { Intent = Clone(intent), WrittenAt = nowSeconds }; } } public static void EnsureRegistered() { try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null || (_registered && _registeredOn == instance)) { return; } instance.Register<ZPackage>("FT_MemberIntents", (Action<long, ZPackage>)OnRouted); _registered = true; _registeredOn = instance; if (!_loggedRegister) { _loggedRegister = true; FactionTacticsLog.Debug("[FT] IntentRpcSync registered RPC 'FT_MemberIntents'"); try { Debug.Log((object)"[FactionTactics] IntentRpcSync registered 'FT_MemberIntents' (RPC hybrid transport)."); return; } catch { return; } } FactionTacticsLog.Debug("[FT] IntentRpcSync re-registered RPC 'FT_MemberIntents' (ZRoutedRpc instance changed)."); } catch (Exception ex) { FactionTacticsLog.Debug("IntentRpcSync.EnsureRegistered failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void FlushBroadcast() { //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Expected O, but got Unknown EnsureRegistered(); if (Pending.Count == 0 || ZRoutedRpc.instance == null) { return; } try { List<KeyValuePair<long, PendingEntry>> list = new List<KeyValuePair<long, PendingEntry>>(Pending.Count); foreach (KeyValuePair<long, PendingEntry> item in Pending) { list.Add(item); } Pending.Clear(); if (list.Count == 0) { return; } ZPackage val = new ZPackage(); val.Write(3); val.Write(list.Count); foreach (KeyValuePair<long, PendingEntry> item2 in list) { WriteOne(val, item2.Key, item2.Value.Intent, item2.Value.WrittenAt); RpcIntentsSent++; } byte[] array = val.GetArray(); int num = 0; long num2 = 0L; try { num2 = ZNet.GetUID(); } catch { } try { if ((Object)(object)ZNet.instance != (Object)null) { List<ZNetPeer> peers = ZNet.instance.GetPeers(); if (peers != null) { for (int i = 0; i < peers.Count; i++) { ZNetPeer val2 = peers[i]; if (val2 != null && val2.m_uid != 0L && (num2 == 0L || val2.m_uid != num2)) { ZRoutedRpc.instance.InvokeRoutedRPC(val2.m_uid, "FT_MemberIntents", new object[1] { (object)new ZPackage(array) }); num++; RpcPeerInvokes++; } } } } } catch (Exception ex) { FactionTacticsLog.Debug("IntentRpcSync.FlushBroadcast peer iterate: " + ex.GetType().Name + ": " + ex.Message); } try { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FT_MemberIntents", new object[1] { (object)new ZPackage(array) }); } catch (Exception ex2) { FactionTacticsLog.Debug("IntentRpcSync.FlushBroadcast Everybody: " + ex2.GetType().Name + ": " + ex2.Message); } RpcBatchesSent++; if (num == 0) { FactionTacticsLog.Debug($"[FT] IntentRpcSync FlushBroadcast: no peers targeted; Everybody backup only (intents={list.Count})."); } } catch (Exception ex3) { FactionTacticsLog.Debug("IntentRpcSync.FlushBroadcast failed: " + ex3.GetType().Name + ": " + ex3.Message); } } private static void OnRouted(long senderPeerId, ZPackage pkg) { RpcPacketsReceived++; if (pkg == null) { LogFirstPacket(senderPeerId, -1, "null pkg"); return; } try { int num = pkg.ReadInt(); if (num != 3) { RpcSchemaMismatches++; LogFirstPacket(senderPeerId, -1, $"schema mismatch rpc={num}"); FactionTacticsLog.Debug($"[FT] IntentRpcSync schema mismatch rpc={num} local={3}"); return; } int num2 = pkg.ReadInt(); LogFirstPacket(senderPeerId, num2, null); if (num2 < 0 || num2 > 4096) { return; } float time = Time.time; for (int i = 0; i < num2; i++) { if (!TryReadOne(pkg, out long id, out MemberIntent intent, out float writtenAt)) { break; } if (id != 0L && intent != null) { if (!IntentZdoCodec.IsFresh(writtenAt, time)) { writtenAt = time; } Cache[id] = new CacheEntry { Intent = intent, WrittenAt = writtenAt }; RpcIntentsReceived++; } } } catch (Exception ex) { FactionTacticsLog.Debug("IntentRpcSync.OnRouted failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void LogFirstPacket(long senderPeerId, int count, string? note) { if (_loggedFirstPacket) { return; } _loggedFirstPacket = true; string text = (string.IsNullOrEmpty(note) ? "" : (" " + note)); string text2 = $"[FactionTactics] RPC OnRouted first packet sender={senderPeerId} count={count} packets={RpcPacketsReceived}{text}"; try { Debug.Log((object)text2); } catch { } try { FactionTacticsLog.Debug(text2); } catch { } } private static void WriteOne(ZPackage pkg, long id, MemberIntent intent, float writtenAt) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) pkg.Write(id); pkg.Write((int)IntentZdoCodec.PackFlags(intent)); pkg.Write((int)intent.OrderKind); pkg.Write((int)intent.Formation); pkg.Write((int)intent.Stance); pkg.Write((int)intent.Role); pkg.Write(intent.DesiredPosition); pkg.Write(intent.FocusTargetId.GetValueOrDefault()); pkg.Write(writtenAt); pkg.Write(IntentZdoSync.StableHash(intent.SquadId)); pkg.Write(intent.SlotIndex); } private static bool TryReadOne(ZPackage pkg, out long id, out MemberIntent intent, out float writtenAt) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) id = 0L; intent = null; writtenAt = 0f; try { id = pkg.ReadLong(); IntentZdoCodec.IntentFlags flags = (IntentZdoCodec.IntentFlags)pkg.ReadInt(); DoctrineOrderKind orderKind = (DoctrineOrderKind)pkg.ReadInt(); FormationType formation = (FormationType)pkg.ReadInt(); StanceType stance = (StanceType)pkg.ReadInt(); SquadRole role = (SquadRole)pkg.ReadInt(); Vector3 desiredPosition = pkg.ReadVector3(); long num = pkg.ReadLong(); writtenAt = pkg.ReadSingle(); pkg.ReadInt(); int slotIndex = pkg.ReadInt(); if (!IntentZdoCodec.IsActive(flags)) { return true; } intent = new MemberIntent { OrderKind = orderKind, Formation = formation, Stance = stance, Role = role, DesiredPosition = desiredPosition, FocusTargetId = ((num != 0L) ? new long?(num) : ((long?)null)), SquadId = "", SlotIndex = slotIndex }; IntentZdoCodec.ApplyFlags(intent, flags); return true; } catch { return false; } } public static bool TryGet(long zdoidPacked, float nowSeconds, out MemberIntent intent) { intent = null; if (zdoidPacked == 0L) { return false; } if (!Cache.TryGetValue(zdoidPacked, out var value)) { return false; } if (!IntentZdoCodec.IsFresh(value.WrittenAt, nowSeconds)) { return false; } intent = value.Intent; RpcCacheHits++; if (!_loggedFirstApply) { _loggedFirstApply = true; try { Debug.Log((object)$"[FactionTactics] RPC intent applied (first) zdoid={zdoidPacked} order={intent.OrderKind} formation={intent.Formation}"); } catch { } FactionTacticsLog.Debug($"[FT] first RPC intent applied zdoid={zdoidPacked} order={intent.OrderKind}"); } return true; } public static bool TryGetFromMonsterAI(MonsterAI ai, float nowSeconds, out MemberIntent intent) { intent = null; try { return TryGet(ValheimIds.FromCharacter(ValheimIds.GetCharacter((BaseAI?)(object)ai)), nowSeconds, out intent); } catch { return false; } } private static MemberIntent Clone(MemberIntent src) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) return new MemberIntent { SquadId = src.SquadId, OrderKind = src.OrderKind, Formation = src.Formation, Stance = src.Stance, Role = src.Role, DesiredPosition = src.DesiredPosition, FocusTargetId = src.FocusTargetId, HoldGround = src.HoldGround, PreferRun = src.PreferRun, AllowVanillaChase = src.AllowVanillaChase, PreferKeepRange = src.PreferKeepRange, AssaultWallBreaker = src.AssaultWallBreaker, AssaultMissileCover = src.AssaultMissileCover, AllowVanillaStructure = src.AllowVanillaStructure, DeathRush = src.DeathRush, SlotIndex = src.SlotIndex }; } } public sealed class MemberIntent { public string SquadId { get; set; } = ""; public DoctrineOrderKind OrderKind { get; set; } public FormationType Formation { get; set; } public StanceType Stance { get; set; } public SquadRole Role { get; set; } public Vector3 DesiredPosition { get; set; } public long? FocusTargetId { get; set; } public bool HoldGround { get; set; } public bool PreferRun { get; set; } public bool AllowVanillaChase { get; set; } public bool PreferKeepRange { get; set; } public bool AssaultWallBreaker { get; set; } public bool AssaultMissileCover { get; set; } public bool AllowVanillaStructure { get; set; } public bool DeathRush { get; set; } public int SlotIndex { get; set; } } public enum FormationType { Loose, Line, ShieldWall, Wedge, Skirmish, Orb } public enum StanceType { Passive, Defensive, Aggressive, Fleeing } public static class FtVersion { public const string ProductVersion = "1.0.13"; public const int IntentSchemaVersion = 3; } } namespace FactionTactics.Client { [HarmonyPatch(typeof(MonsterAI))] public static class ClientMonsterAI_UpdateAI_Patch { public static long UpdateHits { get; private set; } public static long DriveHits { get; private set; } public static long SchemaRejects { get; private set; } public static long RpcDriveHits { get; private set; } [HarmonyPrefix] [HarmonyPatch("UpdateAI")] public static bool Prefix(MonsterAI __instance, float dt) { UpdateHits++; if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated()) { return true; } if (!IntentZdoSync.IsNetOwner(__instance)) { return true; } float time = Time.time; MemberIntent intent; bool flag = IntentRpcSync.TryGetFromMonsterAI(__instance, time, out intent); if (!flag && !IntentZdoSync.TryReadFromMonsterAI(__instance, time, out intent)) { if (IntentZdoSync.SchemaMismatches > SchemaRejects) { SchemaRejects = IntentZdoSync.SchemaMismatches; } return true; } if (CombatAuthority.ShouldReleaseToVanillaUpdateAI(intent)) { return true; } CombatDriver.Drive(__instance, intent, dt); DriveHits++; if (flag) { RpcDriveHits++; } return false; } } [HarmonyPatch(typeof(BaseAI), "MoveTo")] public static class ClientBaseAI_MoveTo_Patch { [HarmonyPrefix] public static bool Prefix(BaseAI __instance, float dt, ref Vector3 point, float dist, bool run) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) MonsterAI val = (MonsterAI)(object)((__instance is MonsterAI) ? __instance : null); if (val == null) { return true; } if (!IntentZdoSync.IsNetOwner(val)) { return true; } float time = Time.time; if (!IntentRpcSync.TryGetFromMonsterAI(val, time, out MemberIntent intent) && !IntentZdoSync.TryReadFromMonsterAI(val, time, out intent)) { return true; } if (CombatAuthority.ShouldAllowVanillaMoveTo(intent)) { return true; } if (intent.HoldGround) { __instance.StopMoving(); return false; } if (intent.DesiredPosition != Vector3.zero && (intent.PreferKeepRange || CombatDriver.IsLineFront(intent))) { point = intent.DesiredPosition; } return true; } } [HarmonyPatch(typeof(ZNet), "Awake")] public static class ClientZNet_Awake_RegisterIntentRpc_Patch { [HarmonyPostfix] public static void Postfix() { IntentRpcSync.EnsureRegistered(); } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] public static class ClientZNet_OnNewConnection_RegisterIntentRpc_Patch { [HarmonyPostfix] public static void Postfix() { IntentRpcSync.EnsureRegistered(); } } [HarmonyPatch(typeof(ZNet), "Update")] public static class ClientZNet_Update_RegisterIntentRpc_Patch { [HarmonyPostfix] public static void Postfix() { if (!IntentRpcSync.IsRegistered) { IntentRpcSync.EnsureRegistered(); } } } [HarmonyPatch(typeof(Game), "Update")] public static class ClientGame_Update_RegisterIntentRpc_Patch { [HarmonyPostfix] public static void Postfix() { if (!IntentRpcSync.IsRegistered) { IntentRpcSync.EnsureRegistered(); } } } internal static class ClientConsoleCommands { [CompilerGenerated] private static class <>O { public static ConsoleOptionsFetcher <0>__TabOptions; } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static Action<string> <>9__1_0; public static ConsoleEvent <>9__1_1; internal void <Register>b__1_0(string line) { try { Console instance = Console.instance; if ((Object)(object)instance != (Object)null) { instance.Print(line); } } catch { } try { ManualLogSource log = ClientPlugin.Log; if (log != null) { log.LogInfo((object)line); } } catch { } } internal void <Register>b__1_1(ConsoleEventArgs args) { Run(args); } } private static bool _registered; public static void Register() { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown if (_registered) { return; } if (Type.GetType("FactionTactics.Plugin, FactionTactics", throwOnError: false) != null) { ManualLogSource log = ClientPlugin.Log; if (log != null) { log.LogInfo((object)"FactionTactics.Client: skipping ft console (server plugin present)."); } return; } _registered = true; FtConfigRpc.LocalPrint = delegate(string line) { try { Console instance = Console.instance; if ((Object)(object)instance != (Object)null) { instance.Print(line); } } catch { } try { ManualLogSource log3 = ClientPlugin.Log; if (log3 != null) { log3.LogInfo((object)line); } } catch { } }; object obj = <>c.<>9__1_1; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { Run(args); }; <>c.<>9__1_1 = val; obj = (object)val; } object obj2 = <>O.<0>__TabOptions; if (obj2 == null) { ConsoleOptionsFetcher val2 = TabOptions; <>O.<0>__TabOptions = val2; obj2 = (object)val2; } new ConsoleCommand("ft", "Faction Tactics admin: ft help | get <key> | set <key> <value> | reload | status (RPC to dedicated)", (ConsoleEvent)obj, false, false, false, false, true, false, (ConsoleOptionsFetcher)obj2, true, false, true); ManualLogSource log2 = ClientPlugin.Log; if (log2 != null) { log2.LogInfo((object)"FactionTactics.Client: registered admin 'ft' (RPC to dedicated)."); } } private static List<string> TabOptions() { return new List<string> { "help", "get", "set", "reload", "status", "HoldAttackCooldown", "SwingStaggerMs", "HoldAttackRangeFactor", "FormationReshuffleSeconds", "FormationCasualtyReshuffle", "ChargeMaxSeconds", "OrderMinDwellSeconds", "OrderScoreHysteresis", "RomanWallOuter", "RomanWallInner", "RomanStandoffDistance", "RomanStandoffHoldMin", "RomanStandoffHoldMax", "RomanContactSwingRange", "RomanRetreatPauseSeconds", "VikingStandoffDistance", "VikingIndoorsStandoff", "VikingStandoffHoldMin", "VikingStandoffHoldMax", "VikingContactSwingRange", "VikingRetreatPauseSeconds", "FlankSplitMeters", "IsolateBuddyMeters", "DiscoveryBurstOnSpawn", "DiscoveryBurstSeconds", "TickIntervalSeconds", "MinSquadSize", "DiscoveryRadius" }; } private static void Run(ConsoleEventArgs args) { if (!FtConfigRpc.LocalPlayerIsAdmin()) { args.Context.AddString("[ft] admin only (ZNet admin list / host)"); return; } switch ((args.Length >= 2) ? args[1].ToLowerInvariant() : "help") { case "help": case "?": PrintLocalHelp(args); FtConfigRpc.SendCmd("help"); break; case "get": if (args.Length < 3) { args.Context.AddString("[ft] usage: ft get <key>"); } else { FtConfigRpc.SendCmd("get", args[2]); } break; case "set": { if (args.Length < 4) { args.Context.AddString("[ft] usage: ft set <key> <value>"); break; } string text = args[2]; string text2 = string.Join(" ", args.Args.Skip(3)); args.Context.AddString("[ft] requesting server set " + text + "=" + text2 + " …"); FtConfigRpc.SendCmd("set", text, text2); break; } case "reload": args.Context.AddString("[ft] requesting server reload …"); FtConfigRpc.SendCmd("reload"); break; case "status": PrintLocalStatus(args); FtConfigRpc.SendCmd("status"); break; default: args.Context.AddString("[ft] unknown subcommand '" + args[1] + "' — try: ft help"); break; } } private static void PrintLocalHelp(ConsoleEventArgs args) { args.Context.AddString("[ft] FactionTactics.Client 1.0.13 (admin RPC → dedicated)"); args.Context.AddString(" ft help | get <key> | set <key> <value> | reload | status"); args.Context.AddString(" Mutating commands apply on the dedicated server PluginConfig (persisted)."); args.Context.AddString(" Phase A/B knobs: HoldAttackCooldown, SwingStaggerMs, FormationReshuffleSeconds,"); args.Context.AddString(" ChargeMaxSeconds, OrderMinDwellSeconds, OrderScoreHysteresis,"); args.Context.AddString(" RomanStandoffDistance, RomanStandoffHoldMin/Max, RomanContactSwingRange, RomanRetreatPauseSeconds,"); args.Context.AddString(" VikingStandoffDistance, VikingIndoorsStandoff, VikingContactSwingRange, FlankSplitMeters, DiscoveryBurst*"); args.Context.AddString($" Local executor tuning: HoldAttackCooldown={CombatTuning.HoldAttackCooldown} SwingStaggerMs={CombatTuning.SwingStaggerMs}"); } private static void PrintLocalStatus(ConsoleEventArgs args) { args.Context.AddString(string.Format("[ft] FactionTactics.Client {0} product={1} schema=v{2}", "1.0.13", "1.0.13", 3)); args.Context.AddString($"[ft] local: swings={CombatDriver.Swings} holdBlocks={CombatDriver.HoldBlocks} drives={ClientMonsterAI_UpdateAI_Patch.DriveHits} rpcDrives={ClientMonsterAI_UpdateAI_Patch.RpcDriveHits}"); args.Context.AddString($"[ft] zdo: readsOk={IntentZdoSync.ReadsOk} stale={IntentZdoSync.ReadsStale} mismatch={IntentZdoSync.SchemaMismatches} rpcRecv={IntentRpcSync.RpcIntentsReceived} rpcPkts={IntentRpcSync.RpcPacketsReceived} rpcHits={IntentRpcSync.RpcCacheHits}"); args.Context.AddString($"[ft] tuning: HoldAttackCooldown={CombatTuning.HoldAttackCooldown} SwingStaggerMs={CombatTuning.SwingStaggerMs} RangeFactor={CombatTuning.HoldAttackRangeFactor}"); args.Context.AddString("[ft] requesting server status …"); } } [BepInPlugin("com.nate.factiontactics.client", "FactionTactics.Client", "1.0.13")] public class ClientPlugin : BaseUnityPlugin { public const string PluginGuid = "com.nate.factiontactics.client"; public const string PluginName = "FactionTactics.Client"; public const string PluginVersion = "1.0.13"; private Harmony? _harmony; internal static ClientPlugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; FactionTacticsLog.DebugSink = delegate(string msg) { Log.LogDebug((object)msg); }; if (IsDedicatedProcess()) { Log.LogInfo((object)"FactionTactics.Client 1.0.13: dedicated process detected — client executor idle (install FactionTactics server DLL for commander)."); return; } _harmony = new Harmony("com.nate.factiontactics.client"); _harmony.PatchAll(typeof(ClientPlugin).Assembly); try { IntentRpcSync.EnsureRegistered(); } catch { } try { FtConfigRpc.EnsureRegistered(); } catch { } ClientConsoleCommands.Register(); Log.LogInfo((object)"FactionTactics.Client 1.0.13 loaded — owning-client combat executor (RPC intents → CombatDriver swing gate; admin ft RPC). Install alongside/without server DLL on players."); } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private static bool IsDedicatedProcess() { try { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsDedicated(); } } catch { } string[] commandLineArgs = Environment.GetCommandLineArgs(); foreach (string a in commandLineArgs) { if (string.Equals(a, "-batchmode", StringComparison.OrdinalIgnoreCase)) { return true; } if (string.Equals(a, "-nographics", StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } } }