public static void Postfix(ref CalledShotAttackOrderInfo __result, AbstractActor attackingUnit, int enemyUnitIndex)
        {
            Mod.Log.Trace?.Write("AE:CCSLTC entered");

            ICombatant combatant = attackingUnit.BehaviorTree.enemyUnits[enemyUnitIndex];

            if (combatant is AbstractActor targetActor)
            {
                // Prevents blips from being the targets of called shots
                VisibilityLevel targetVisibility = attackingUnit.VisibilityToTargetUnit(targetActor);
                if (targetVisibility < VisibilityLevel.LOSFull)
                {
                    Mod.Log.Info?.Write($"Target {CombatantUtils.Label(combatant)} is a blip, cannot be targeted by AI called shot");
                    __result = null;
                    return;
                }

                float          distance      = Vector3.Distance(attackingUnit.CurrentPosition, targetActor.CurrentPosition);
                bool           hasVisualScan = VisualLockHelper.GetVisualScanRange(attackingUnit) >= distance;
                SensorScanType sensorScan    = SensorLockHelper.CalculateSharedLock(targetActor, attackingUnit);
                if (sensorScan < SensorScanType.ArmorAndWeaponType && !hasVisualScan)
                {
                    Mod.Log.Info?.Write($"Target {CombatantUtils.Label(targetActor)} sensor info {sensorScan} is less than SurfaceScan and outside visualID, cannot be targeted by AI called shot");
                    __result = null;
                    return;
                }
            }
        }
        public static void Postfix(Vehicle __instance, ref Text __result, VisibilityLevel visLevel)
        {
            if (__instance == null)
            {
                return;
            }
            ;

            /*
             *  Vehicle.UnitName = VehicleDef.Chassis.Description.Name ->
             *      Alacorn Mk.VI-P / vehicledef_ARES_CLAN / Demolisher II / Galleon GAL-102
             *  Vehicle.NickName = VehicleDef.Description.Name ->
             *      Pirate Alacorn Gauss Carrier / Ares / Demolisher II`
             *      VehicleDef.Description.Id ->
             *          / / vehicledef_DEMOLISHER-II / vehicledef_GALLEON_GAL102
             */
            if (__instance.Combat.HostilityMatrix.IsLocalPlayerEnemy(__instance.team.GUID))
            {
                string chassisName = __instance.UnitName;
                string fullName    = __instance.Nickname;

                SensorScanType scanType = SensorLockHelper.CalculateSharedLock(__instance, null);
                if (scanType < SensorScanType.ArmorAndWeaponType)
                {
                    bool hasVisualScan = VisualLockHelper.CanSpotTargetUsingCurrentPositions(ModState.LastPlayerActorActivated, __instance);
                    if (hasVisualScan)
                    {
                        scanType = SensorScanType.ArmorAndWeaponType;
                    }
                }

                Text response = CombatNameHelper.GetTurretOrVehicleDetectionLabel(visLevel, scanType, fullName, chassisName, true);
                __result = response;
            }
        }
 public static void Postfix(LineOfSight __instance, ref float __result, AbstractActor source, CombatGameState ___Combat)
 {
     if (__instance != null && source != null)
     {
         __result = VisualLockHelper.GetSpotterRange(source);
     }
 }
 public static void Postfix(LineOfSight __instance, ref float __result, AbstractActor source, ICombatant target)
 {
     if (__instance != null && source != null)
     {
         __result = VisualLockHelper.GetAdjustedSpotterRange(source, target);
     }
 }
        public static void Postfix(Turret __instance, ref Text __result, VisibilityLevel visLevel)
        {
            if (__instance == null)
            {
                return;
            }

            /*
             *  Turret.UnitName = return (this.TurretDef == null) ? "UNDEFINED" : this.TurretDef.Chassis.Description.Name ->
             *  Turret.NickName = (this.TurretDef == null) ? "UNDEFINED" : this.TurretDef.Description.Name ->
             */
            if (__instance.Combat.HostilityMatrix.IsLocalPlayerEnemy(__instance.team.GUID))
            {
                string chassisName = __instance.UnitName;
                string fullName    = __instance.Nickname;

                SensorScanType scanType = SensorLockHelper.CalculateSharedLock(__instance, null);
                if (scanType < SensorScanType.ArmorAndWeaponType)
                {
                    bool hasVisualScan = VisualLockHelper.CanSpotTargetUsingCurrentPositions(ModState.LastPlayerActorActivated, __instance);
                    if (hasVisualScan)
                    {
                        scanType = SensorScanType.ArmorAndWeaponType;
                    }
                }

                Text response = CombatNameHelper.GetTurretOrVehicleDetectionLabel(visLevel, scanType, fullName, chassisName, false);
                __result = response;
            }
        }
        public static bool Prefix(LineOfSight __instance, ref VisibilityLevel __result,
                                  AbstractActor source, Vector3 sourcePosition, ICombatant target, Vector3 targetPosition, Quaternion targetRotation)
        {
            Mod.Log.Trace?.Write($"LOS:GVTTWPAR: source:{CombatantUtils.Label(source)} ==> target:{CombatantUtils.Label(target)}");

            // Skip if we aren't ready to process
            // TODO: Is this necessary anymore?
            //if (State.TurnDirectorStarted == false || (target as AbstractActor) == null) { return true;  }

            AbstractActor sourceActor = source as AbstractActor;

            // TODO: Handle buildings here
            bool sourceHasLineOfSight = VisualLockHelper.CanSpotTarget(sourceActor, sourcePosition, target, targetPosition, targetRotation, __instance);

            if (sourceHasLineOfSight)
            {
                __result = VisibilityLevel.LOSFull;
            }
            else
            {
                VisibilityLevel sensorsVisibility = VisibilityLevel.None;
                if (ModState.TurnDirectorStarted)
                {
                    SensorScanType sensorLock = SensorLockHelper.CalculateSensorLock(sourceActor, sourcePosition, target, targetPosition);
                    sensorsVisibility = sensorLock.Visibility();
                }
                __result = sensorsVisibility;
            }

            //Mod.Log.Trace?.Write($"LOS:GVTTWPAR - [{__result}] visibility for source:{CombatantUtils.Label(source)} ==> target:{CombatantUtils.Label(target)}");
            return(false);
        }
示例#7
0
        public static void ObfuscateArmorAndStructText(AbstractActor target, TextMeshProUGUI armorHover, TextMeshProUGUI structHover)
        {
            if (target == null)
            {
                Mod.Log.Warn?.Write("Helper::HideArmorAndStructure - target is null!");
            }
            if (armorHover == null)
            {
                Mod.Log.Warn?.Write("Helper::HideArmorAndStructure - armorHover is null!");
            }
            if (structHover == null)
            {
                Mod.Log.Warn?.Write("Helper::HideArmorAndStructure - structHover is null!");
            }

            try
            {
                SensorScanType scanType      = SensorLockHelper.CalculateSharedLock(target, ModState.LastPlayerActorActivated);
                bool           hasVisualScan = VisualLockHelper.CanSpotTarget(
                    ModState.LastPlayerActorActivated, ModState.LastPlayerActorActivated.CurrentPosition,
                    target, target.CurrentPosition, target.CurrentRotation, target.Combat.LOS);

                string armorText;
                string structText;
                if (scanType >= SensorScanType.StructAndWeaponID)
                {
                    // See all values
                    armorText  = armorHover.text;
                    structText = structHover.text;
                }
                else if (scanType >= SensorScanType.ArmorAndWeaponType || hasVisualScan)
                {
                    // See max armor, max struct
                    string rawArmor = armorHover.text;
                    string maxArmor = rawArmor.Split('/')[1];

                    string rawStruct = structHover.text;
                    string maxStruct = rawStruct.Split('/')[1];

                    armorText  = $"? / {maxArmor}";
                    structText = $"? / {maxStruct}";
                }
                else
                {
                    // See ? / ?
                    armorText  = "? / ?";
                    structText = "? / ?";
                }

                // TODO: Sensor lock should give you an exact amount at the point you're locked
                armorHover.SetText(armorText);
                structHover.SetText(structText);
            }
            catch (Exception e)
            {
                Mod.Log.Error?.Write(e, $"Failed to obfuscate armor and structure text for unit: {CombatantUtils.Label(target)}" +
                                     $" from position of last-activated actor: {CombatantUtils.Label(ModState.LastPlayerActorActivated)}.");
            }
        }
        public static void Prefix(CombatHUDWeaponPanel __instance, AbstractActor ___displayedActor)
        {
            if (__instance == null || ___displayedActor == null)
            {
                return;
            }
            Mod.Log.Trace?.Write("CHUDWP:RDW - entered.");

            Traverse targetT        = Traverse.Create(__instance).Property("target");
            Traverse hoveredTargetT = Traverse.Create(__instance).Property("hoveredTarget");

            Traverse       HUDT        = Traverse.Create(__instance).Property("HUD");
            CombatHUD      HUD         = HUDT.GetValue <CombatHUD>();
            SelectionState activeState = HUD.SelectionHandler.ActiveState;
            ICombatant     target;

            if (activeState != null && activeState is SelectionStateMove)
            {
                target = hoveredTargetT.GetValue <ICombatant>();
                if (target == null)
                {
                    target = targetT.GetValue <ICombatant>();
                }
            }
            else
            {
                target = targetT.GetValue <ICombatant>();
                if (target == null)
                {
                    target = hoveredTargetT.GetValue <ICombatant>();
                }
            }

            if (target == null)
            {
                return;
            }

            EWState attackerState = new EWState(___displayedActor);

            Mod.Log.Debug?.Write($"Attacker ({CombatantUtils.Label(___displayedActor)} => EWState: {attackerState}");
            bool canSpotTarget = VisualLockHelper.CanSpotTarget(___displayedActor, ___displayedActor.CurrentPosition,
                                                                target, target.CurrentPosition, target.CurrentRotation, ___displayedActor.Combat.LOS);
            SensorScanType sensorScan = SensorLockHelper.CalculateSharedLock(target, ___displayedActor);

            Mod.Log.Debug?.Write($"  canSpotTarget: {canSpotTarget}  sensorScan: {sensorScan}");

            if (target is AbstractActor targetActor)
            {
                EWState targetState = new EWState(targetActor);
                Mod.Log.Debug?.Write($"Target ({CombatantUtils.Label(targetActor)} => EWState: {targetState}");
            }
        }
        public static bool Prefix(SelectionStateFire __instance, ref bool __result, ICombatant combatant)
        {
            Mod.Log.Trace?.Write("SSF:PCC:PRE entered");

            if (__instance != null && combatant != null && combatant is AbstractActor targetActor && __instance.SelectedActor != null)
            {
                CombatGameState Combat           = __instance.SelectedActor.Combat;
                bool            targetIsFriendly = Combat.HostilityMatrix.IsFriendly(combatant.team.GUID, Combat.LocalPlayerTeamGuid);
                if (targetIsFriendly)
                {
                    Mod.Log.Trace?.Write("Friendly target, skipping check");
                    return(true);
                }

                EWState targetState   = new EWState(targetActor);
                EWState attackerState = new EWState(__instance.SelectedActor);

                if (__instance.SelectionType == SelectionType.FireMorale)
                {
                    // Prevents blips from being the targets of called shots
                    VisibilityLevel targetVisibility = __instance.SelectedActor.VisibilityToTargetUnit(targetActor);
                    if (targetVisibility < VisibilityLevel.LOSFull)
                    {
                        Mod.Log.Info?.Write($"Target {CombatantUtils.Label(combatant)} is a blip, cannot be targeted by called shot");
                        __result = false;
                        return(false);
                    }

                    float          distance      = Vector3.Distance(__instance.SelectedActor.CurrentPosition, targetActor.CurrentPosition);
                    bool           hasVisualScan = VisualLockHelper.GetVisualScanRange(__instance.SelectedActor) >= distance;
                    SensorScanType sensorScan    = SensorLockHelper.CalculateSharedLock(targetActor, __instance.SelectedActor);
                    if (sensorScan < SensorScanType.ArmorAndWeaponType && !hasVisualScan)
                    {
                        Mod.Log.Info?.Write($"Target {CombatantUtils.Label(targetActor)} sensor info {sensorScan} is less than SurfaceScan and range:{distance} outside visualScan range, cannot be targeted by called shot");
                        __result = false;
                        return(false);
                    }
                }
                else if (__instance.SelectionType == SelectionType.FireMulti)
                {
                    if (targetState.HasStealth() || targetState.HasMimetic())
                    {
                        Mod.Log.Info?.Write($"Target {CombatantUtils.Label(targetActor)} has stealth, cannot be multi-targeted!");
                        __result = false;
                        return(false);
                    }
                }
            }

            __result = false;
            return(true);
        }
示例#10
0
        public void TestVisibility_NoEffects()
        {
            Mech attacker = TestHelper.BuildTestMech();
            Mech target   = TestHelper.BuildTestMech();

            attacker.StatCollection.AddStatistic <float>("SpotterDistanceMultiplier", 1f);
            attacker.StatCollection.AddStatistic <float>("SpotterDistanceAbsolute", 0f);

            target.StatCollection.AddStatistic <float>("SpottingVisibilityMultiplier", 1f);
            target.StatCollection.AddStatistic <float>("SpottingVisibilityAbsolute", 0f);

            EWState attackerState = new EWState(attacker);
            EWState targetState   = new EWState(target);

            Assert.AreEqual(1.0f, VisualLockHelper.GetTargetVisibility(target, attackerState));
        }
示例#11
0
        public void TestVisibility_Shutdown()
        {
            Mech attacker = TestHelper.BuildTestMech();
            Mech target   = TestHelper.BuildTestMech();

            attacker.StatCollection.AddStatistic <float>("SpotterDistanceMultiplier", 1f);
            attacker.StatCollection.AddStatistic <float>("SpotterDistanceAbsolute", 0f);

            target.StatCollection.AddStatistic <float>("SpottingVisibilityMultiplier", 1f);
            target.StatCollection.AddStatistic <float>("SpottingVisibilityAbsolute", 0f);

            Traverse isShutdownT = Traverse.Create(target).Field("_isShutDown");

            isShutdownT.SetValue(true);

            EWState attackerState = new EWState(attacker);
            EWState targetState   = new EWState(target);

            Assert.AreEqual(0.5f, VisualLockHelper.GetTargetVisibility(target, attackerState));
        }
示例#12
0
        static void UpdateSensorAndVisualsIcons(MarkGOContainer container, AbstractActor displayedActor, AbstractActor lastActivated, bool isPlayer)
        {
            // Sensors and Visuals are only shown for non-local players
            if (!isPlayer)
            {
                // Check sensors
                bool hasSensorLock = SensorLockHelper.CalculateSharedLock(displayedActor, lastActivated) > SensorScanType.NoInfo;
                container.SensorsMark.SetActive(true);
                SVGImage sensorsImage = container.SensorsMark.GetComponent <SVGImage>();
                if (hasSensorLock)
                {
                    Mod.Log.Debug?.Write($" - Can sensors detect target, setting icon to green.");
                    sensorsImage.color = Color.green;
                }
                else
                {
                    Mod.Log.Debug?.Write($" - Can not sensors detect target, setting icon to red.");
                    sensorsImage.color = Color.red;
                }

                bool canSpotTarget = VisualLockHelper.CanSpotTarget(lastActivated, lastActivated.CurrentPosition,
                                                                    displayedActor, displayedActor.CurrentPosition, displayedActor.CurrentRotation, ModState.Combat.LOS);
                SVGImage visualsImage = container.VisualsMark.GetComponent <SVGImage>();
                container.VisualsMark.SetActive(true);
                if (canSpotTarget)
                {
                    Mod.Log.Debug?.Write($" - Can spot target, setting icon to green.");
                    visualsImage.color = Color.green;
                }
                else
                {
                    Mod.Log.Debug?.Write($" - Cannot spot target, setting icon to red.");
                    visualsImage.color = Color.red;
                }
            }
            else
            {
                container.SensorsMark.SetActive(false);
                container.VisualsMark.SetActive(false);
            }
        }
示例#13
0
        public void TestMimetic_Visibility_DecayThreeStep()
        {
            Mech attacker = TestHelper.BuildTestMech();
            Mech target   = TestHelper.BuildTestMech();

            target.CurrentPosition = new Vector3(180f, 0, 0);
            Traverse previousPositionT = Traverse.Create(target).Field("previousPosition");

            previousPositionT.SetValue(new Vector3(0f, 0f, 0f));

            attacker.StatCollection.AddStatistic <float>("SpotterDistanceMultiplier", 1f);
            attacker.StatCollection.AddStatistic <float>("SpotterDistanceAbsolute", 0f);

            target.StatCollection.AddStatistic <float>("SpottingVisibilityMultiplier", 1f);
            target.StatCollection.AddStatistic <float>("SpottingVisibilityAbsolute", 0f);
            target.StatCollection.Set(ModStats.MimeticEffect, "4_0.10_1_2");

            EWState attackerState = new EWState(attacker);
            EWState targetState   = new EWState(target);

            Assert.AreEqual(0.9f, VisualLockHelper.GetTargetVisibility(target, attackerState), 0.001);
        }
示例#14
0
        public static void Postfix(Mech __instance, ref Text __result, VisibilityLevel visLevel)
        {
            if (__instance == null)
            {
                return;
            }

            /*
             *  Mech.UnitName = MechDef.Chassis.Description.Name -> Shadow Hawk / Atlas / Marauder
             *  Mech.Nickname = Mech.Description.Name -> Shadow Hawk SHD-2D / Atlas AS7-D / Marauder ANU-O
             *  Mech.Description.UIName -> Shadow Hawk SHD-2D / Atlas AS7-D Danielle / Anand ANU-O
             */
            string fullName = __instance.Description.UIName;

            if (__instance.Combat.HostilityMatrix.IsLocalPlayerEnemy(__instance.team.GUID))
            {
                string chassisName = __instance.UnitName;
                string partialName = __instance.Nickname;

                SensorScanType scanType = SensorLockHelper.CalculateSharedLock(__instance, null);
                if (scanType < SensorScanType.ArmorAndWeaponType)
                {
                    bool hasVisualScan = VisualLockHelper.CanSpotTargetUsingCurrentPositions(ModState.LastPlayerActorActivated, __instance);
                    if (hasVisualScan)
                    {
                        scanType = SensorScanType.ArmorAndWeaponType;
                    }
                }

                __result = CombatNameHelper.GetEnemyMechDetectionLabel(visLevel, scanType, fullName, partialName, chassisName);
            }
            else
            {
                string displayName = __instance.DisplayName;

                __result = CombatNameHelper.GetNonHostileMechDetectionLabel(__instance, fullName, displayName);
            }
        }
示例#15
0
        public static void Postfix(CombatHUDTargetingComputer __instance, List <TextMeshProUGUI> ___weaponNames)
        {
            if (__instance == null || __instance.ActivelyShownCombatant == null ||
                __instance.ActivelyShownCombatant.Combat == null || __instance.ActivelyShownCombatant.Combat.HostilityMatrix == null ||
                __instance.WeaponList == null)
            {
                Mod.Log.Debug?.Write($"CHTC:RAI ~~~ TC, target, or WeaponList is null, skipping.");
                return;
            }

            if (ModState.LastPlayerActorActivated == null)
            {
                Mod.Log.Error?.Write("Attempting to refresh ActorInfo, but LastPlayerActorActivated is null. This should never happen!");
            }

            if (__instance.ActivelyShownCombatant.Combat.HostilityMatrix.IsLocalPlayerFriendly(__instance.ActivelyShownCombatant.team.GUID))
            {
                Mod.Log.Debug?.Write($"CHTC:RAI ~~~ target:{CombatantUtils.Label(__instance.ActivelyShownCombatant)} friendly, resetting.");
                __instance.WeaponList.SetActive(true);
                return;
            }

            // Only enemies or neutrals below this point
            Mod.Log.Debug?.Write($"CHTC:RAI ~~~ target:{CombatantUtils.Label(__instance.ActivelyShownCombatant)} is enemy");

            try
            {
                if (__instance.ActivelyShownCombatant is AbstractActor target)
                {
                    float range         = Vector3.Distance(ModState.LastPlayerActorActivated.CurrentPosition, target.CurrentPosition);
                    bool  hasVisualScan = VisualLockHelper.CanSpotTarget(ModState.LastPlayerActorActivated, ModState.LastPlayerActorActivated.CurrentPosition,
                                                                         target, target.CurrentPosition, target.CurrentRotation, target.Combat.LOS);
                    SensorScanType scanType = SensorLockHelper.CalculateSharedLock(target, ModState.LastPlayerActorActivated);
                    Mod.Log.Debug?.Write($"CHTC:RAI ~~~ LastActivated:{CombatantUtils.Label(ModState.LastPlayerActorActivated)} vs. enemy:{CombatantUtils.Label(target)} " +
                                         $"at range: {range} has scanType:{scanType} visualScan:{hasVisualScan}");

                    // Build the CAC side-panel
                    try
                    {
                        BuildCACDialogForTarget(ModState.LastPlayerActorActivated, __instance.ActivelyShownCombatant, range, hasVisualScan, scanType);
                    }
                    catch (Exception e)
                    {
                        Mod.Log.Error?.Write(e, $"Failed to initialize CAC SidePanel for source: {CombatantUtils.Label(ModState.LastPlayerActorActivated)} and " +
                                             $"target: {CombatantUtils.Label(__instance.ActivelyShownCombatant)}!");
                    }

                    if (scanType >= SensorScanType.StructAndWeaponID)
                    {
                        __instance.WeaponList.SetActive(true);
                        SetArmorDisplayActive(__instance, true);
                    }
                    else if (scanType >= SensorScanType.ArmorAndWeaponType || hasVisualScan)
                    {
                        SetArmorDisplayActive(__instance, true);
                        ObfuscateWeaponLabels(___weaponNames, target);

                        // Update the summary display
                        __instance.WeaponList.SetActive(true);
                        Transform       weaponListT  = __instance.WeaponList?.transform?.parent?.Find("tgtWeaponsLabel");
                        GameObject      weaponsLabel = weaponListT.gameObject;
                        TextMeshProUGUI labelText    = weaponsLabel.GetComponent <TextMeshProUGUI>();
                        labelText.SetText(new Text(Mod.LocalizedText.TargetingComputer[ModText.LT_TARG_COMP_UNIDENTIFIED]).ToString());
                    }
                    else
                    {
                        SetArmorDisplayActive(__instance, false);

                        __instance.WeaponList.SetActive(false);
                        Transform  weaponListT  = __instance.WeaponList?.transform?.parent?.Find("tgtWeaponsLabel");
                        GameObject weaponsLabel = weaponListT.gameObject;
                        weaponsLabel.SetActive(false);
                    }
                }
                else if (__instance.ActivelyShownCombatant is BattleTech.Building building)
                {
                    Mod.Log.Debug?.Write($"CHTC:RAI ~~~ target:{CombatantUtils.Label(__instance.ActivelyShownCombatant)} is enemy building");

                    SetArmorDisplayActive(__instance, true);

                    __instance.WeaponList.SetActive(false);
                    Transform  weaponListT  = __instance.WeaponList?.transform?.parent?.Find("tgtWeaponsLabel");
                    GameObject weaponsLabel = weaponListT.gameObject;
                    weaponsLabel.SetActive(false);
                }
                else
                {
                    // WTF
                }
            }
            catch (Exception e)
            {
                Mod.Log.Error?.Write(e, "Failed to RefreshActorInfo!");
            }
        }
示例#16
0
        private static void Postfix(ToHit __instance, ref float __result, AbstractActor attacker, Weapon weapon, ICombatant target,
                                    Vector3 attackPosition, Vector3 targetPosition, LineOfFireLevel lofLevel)
        {
            //Mod.Log.Debug?.Write($"Getting modifiers for attacker:{CombatantUtils.Label(attacker)} " +
            //    $"using weapon:{weapon.Name} vs target:{CombatantUtils.Label(target)} with initial result:{__result}");

            AbstractActor targetActor = target as AbstractActor;

            if (__instance != null && attacker != null && targetActor != null)
            {
                float distance = Vector3.Distance(attackPosition, targetPosition);

                // Cache these
                EWState attackerState = new EWState(attacker);
                EWState targetState   = new EWState(targetActor);

                // If we can't see the target, apply the No Visuals penalty
                bool canSpotTarget    = VisualLockHelper.CanSpotTarget(attacker, attacker.CurrentPosition, target, target.CurrentPosition, target.CurrentRotation, attacker.Combat.LOS);
                int  mimeticMod       = targetState.MimeticAttackMod(attackerState);
                int  eyeballAttackMod = canSpotTarget ? mimeticMod : Mod.Config.Attack.NoVisualsPenalty;

                // Zoom applies independently of visibility (request from Harkonnen)
                int zoomVisionMod = attackerState.GetZoomVisionAttackMod(weapon, distance);
                int zoomAttackMod = attackerState.HasZoomVisionToTarget(weapon, distance, lofLevel) ? zoomVisionMod - mimeticMod : Mod.Config.Attack.NoVisualsPenalty;

                bool hasVisualAttack = (eyeballAttackMod < Mod.Config.Attack.NoVisualsPenalty || zoomAttackMod < Mod.Config.Attack.NoVisualsPenalty);

                // Sensor attack bucket.  Sensors always fallback, so roll everything up and cap
                int narcAttackMod      = targetState.NarcAttackMod(attackerState);
                int tagAttackMod       = targetState.TagAttackMod(attackerState);
                int ecmShieldAttackMod = targetState.ECMAttackMod(attackerState);
                int stealthAttackMod   = targetState.StealthAttackMod(attackerState, weapon, distance);

                bool hasSensorAttack  = SensorLockHelper.CalculateSharedLock(targetActor, attacker) > SensorScanType.NoInfo;
                int  sensorsAttackMod = Mod.Config.Attack.NoSensorsPenalty;
                if (hasSensorAttack)
                {
                    sensorsAttackMod  = 0;
                    sensorsAttackMod -= narcAttackMod;
                    sensorsAttackMod -= tagAttackMod;
                    sensorsAttackMod += ecmShieldAttackMod;
                    sensorsAttackMod += stealthAttackMod;
                }
                if (sensorsAttackMod > Mod.Config.Attack.NoSensorsPenalty)
                {
                    sensorsAttackMod = Mod.Config.Attack.NoSensorsPenalty;
                    hasSensorAttack  = false;
                }

                // Check firing blind
                if (!hasVisualAttack && !hasSensorAttack)
                {
                    __result += Mod.Config.Attack.FiringBlindPenalty;
                }
                else
                {
                    __result += (zoomAttackMod < eyeballAttackMod) ? zoomAttackMod : eyeballAttackMod;

                    if (attackerState.HasHeatVisionToTarget(weapon, distance))
                    {
                        __result += attackerState.GetHeatVisionAttackMod(targetActor, distance, weapon);
                    }

                    __result += sensorsAttackMod;
                }
            }
        }
            public static void Postfix(CombatHUDActorInfo __instance, AbstractActor ___displayedActor,
                                       BattleTech.Building ___displayedBuilding, ICombatant ___displayedCombatant)
            {
                if (__instance == null || ___displayedActor == null)
                {
                    return;
                }

                try {
                    bool            isEnemyOrNeutral = false;
                    VisibilityLevel visibilityLevel  = VisibilityLevel.None;
                    if (___displayedCombatant != null)
                    {
                        if (___displayedCombatant.IsForcedVisible)
                        {
                            visibilityLevel = VisibilityLevel.LOSFull;
                        }
                        else if (___displayedBuilding != null)
                        {
                            visibilityLevel = __instance.Combat.LocalPlayerTeam.VisibilityToTarget(___displayedBuilding);
                        }
                        else if (___displayedActor != null)
                        {
                            if (__instance.Combat.HostilityMatrix.IsLocalPlayerFriendly(___displayedActor.team))
                            {
                                visibilityLevel = VisibilityLevel.LOSFull;
                            }
                            else
                            {
                                visibilityLevel  = __instance.Combat.LocalPlayerTeam.VisibilityToTarget(___displayedActor);
                                isEnemyOrNeutral = true;
                            }
                        }
                    }

                    Traverse setGOActiveMethod = Traverse.Create(__instance).Method("SetGOActive", new Type[] { typeof(MonoBehaviour), typeof(bool) });
                    // The actual method should handle allied and friendly units fine, so we can just change it for enemies
                    if (isEnemyOrNeutral && visibilityLevel >= VisibilityLevel.Blip0Minimum && ___displayedActor != null)
                    {
                        SensorScanType scanType      = SensorLockHelper.CalculateSharedLock(___displayedActor, ModState.LastPlayerActorActivated);
                        bool           hasVisualScan = VisualLockHelper.CanSpotTarget(ModState.LastPlayerActorActivated, ModState.LastPlayerActorActivated.CurrentPosition,
                                                                                      ___displayedActor, ___displayedActor.CurrentPosition, ___displayedActor.CurrentRotation, ___displayedActor.Combat.LOS);

                        Mod.Log.Debug?.Write($"Updating item visibility for enemy: {CombatantUtils.Label(___displayedActor)} to scanType: {scanType} and " +
                                             $"hasVisualScan: {hasVisualScan} from lastActivated: {CombatantUtils.Label(ModState.LastPlayerActorActivated)}");

                        // Values that are always displayed
                        setGOActiveMethod.GetValue(__instance.NameDisplay, true);
                        setGOActiveMethod.GetValue(__instance.PhaseDisplay, true);

                        if (scanType >= SensorScanType.StructAndWeaponID)
                        {
                            // Show unit summary
                            setGOActiveMethod.GetValue(__instance.DetailsDisplay, true);

                            // Show active state
                            setGOActiveMethod.GetValue(__instance.InspiredDisplay, false);

                            // Show armor and struct
                            setGOActiveMethod.GetValue(__instance.ArmorBar, true);
                            setGOActiveMethod.GetValue(__instance.StructureBar, true);

                            if (___displayedActor as Mech != null)
                            {
                                setGOActiveMethod.GetValue(__instance.StabilityDisplay, true);
                                setGOActiveMethod.GetValue(__instance.HeatDisplay, true);
                            }
                            else
                            {
                                setGOActiveMethod.GetValue(__instance.StabilityDisplay, false);
                                setGOActiveMethod.GetValue(__instance.HeatDisplay, false);
                            }
                        }
                        else if (scanType >= SensorScanType.ArmorAndWeaponType || hasVisualScan)
                        {
                            // Show unit summary
                            setGOActiveMethod.GetValue(__instance.DetailsDisplay, false);

                            // Show active state
                            setGOActiveMethod.GetValue(__instance.InspiredDisplay, false);

                            // Show armor and struct
                            setGOActiveMethod.GetValue(__instance.ArmorBar, true);
                            setGOActiveMethod.GetValue(__instance.StructureBar, true);

                            setGOActiveMethod.GetValue(__instance.StabilityDisplay, false);
                            setGOActiveMethod.GetValue(__instance.HeatDisplay, false);
                        }
                        else
                        {
                            // Hide unit summary
                            setGOActiveMethod.GetValue(__instance.DetailsDisplay, false);

                            // Hide active state
                            setGOActiveMethod.GetValue(__instance.InspiredDisplay, false);

                            // Hide armor and struct
                            setGOActiveMethod.GetValue(__instance.ArmorBar, false);
                            setGOActiveMethod.GetValue(__instance.StructureBar, false);

                            setGOActiveMethod.GetValue(__instance.StabilityDisplay, false);
                            setGOActiveMethod.GetValue(__instance.HeatDisplay, false);
                        }

                        // TODO: DEBUG TESTING
                        if (__instance.MarkDisplay != null)
                        {
                            setGOActiveMethod.GetValue(__instance.MarkDisplay, true);
                        }

                        CombatHUDStateStack stateStack = (CombatHUDStateStack)Traverse.Create(__instance).Property("StateStack").GetValue();
                        setGOActiveMethod.GetValue(stateStack, false);
                    }
                    else
                    {
                        if (__instance.MarkDisplay != null && ___displayedActor != null)
                        {
                            setGOActiveMethod.GetValue(__instance.MarkDisplay, ___displayedActor.IsMarked);
                        }
                    }
                } catch (Exception e) {
                    Mod.Log.Info?.Write($"Error updating item visibility! Error was: {e.Message}");
                }
            }
        private static void Postfix(CombatHUDWeaponSlot __instance, ICombatant target, Weapon ___displayedWeapon, CombatHUD ___HUD)
        {
            if (__instance == null || ___displayedWeapon == null || ___HUD.SelectedActor == null || target == null)
            {
                return;
            }

            Mod.Log.Trace?.Write("CHUDWS:SHC - entered.");

            AbstractActor attacker = __instance.DisplayedWeapon.parent;
            Traverse      AddToolTipDetailMethod = Traverse.Create(__instance).Method("AddToolTipDetail",
                                                                                      new Type[] { typeof(string), typeof(int) });

            if (target is AbstractActor targetActor && __instance.DisplayedWeapon != null)
            {
                float   magnitude     = (attacker.CurrentPosition - target.CurrentPosition).magnitude;
                EWState attackerState = new EWState(attacker);
                EWState targetState   = new EWState(targetActor);

                // If we can't see the target, apply the No Visuals penalty
                bool canSpotTarget    = VisualLockHelper.CanSpotTarget(attacker, attacker.CurrentPosition, target, target.CurrentPosition, target.CurrentRotation, attacker.Combat.LOS);
                int  mimeticMod       = targetState.MimeticAttackMod(attackerState);
                int  eyeballAttackMod = canSpotTarget ? mimeticMod : Mod.Config.Attack.NoVisualsPenalty;

                // Zoom applies independently of visibility (request from Harkonnen)
                LineOfFireLevel lofLevel;
                Vector3         attackPosition = ___HUD.SelectionHandler.ActiveState.PreviewPos;
                if (Vector3.Distance(attacker.CurrentPosition, attackPosition) > 0.1f)
                {
                    Vector3 vector;
                    lofLevel = attacker.Combat.LOS.GetLineOfFire(attacker, attackPosition, target, target.CurrentPosition, target.CurrentRotation, out vector);
                }
                else
                {
                    lofLevel = attacker.VisibilityCache.VisibilityToTarget(target).LineOfFireLevel;
                }

                int zoomVisionMod = attackerState.GetZoomVisionAttackMod(__instance.DisplayedWeapon, magnitude);
                int zoomAttackMod = attackerState.HasZoomVisionToTarget(__instance.DisplayedWeapon, magnitude, lofLevel) ? zoomVisionMod - mimeticMod : Mod.Config.Attack.NoVisualsPenalty;
                Mod.Log.Debug?.Write($"  Visual attack == eyeball: {eyeballAttackMod} mimetic: {mimeticMod} zoomAtack: {zoomAttackMod}");

                bool hasVisualAttack = (eyeballAttackMod < Mod.Config.Attack.NoVisualsPenalty || zoomAttackMod < Mod.Config.Attack.NoVisualsPenalty);

                // Sensor attack bucket.  Sensors always fallback, so roll everything up and cap
                int narcAttackMod      = targetState.NarcAttackMod(attackerState);
                int tagAttackMod       = targetState.TagAttackMod(attackerState);
                int ecmShieldAttackMod = targetState.ECMAttackMod(attackerState);
                int stealthAttackMod   = targetState.StealthAttackMod(attackerState, __instance.DisplayedWeapon, magnitude);
                Mod.Log.Debug?.Write($"  Sensor attack penalties == narc: {narcAttackMod}  tag: {tagAttackMod}  ecmShield: {ecmShieldAttackMod}  stealth: {stealthAttackMod}");

                bool hasSensorAttack  = SensorLockHelper.CalculateSharedLock(targetActor, attacker) > SensorScanType.NoInfo;
                int  sensorsAttackMod = Mod.Config.Attack.NoSensorsPenalty;
                if (hasSensorAttack)
                {
                    sensorsAttackMod  = 0;
                    sensorsAttackMod -= narcAttackMod;
                    sensorsAttackMod -= tagAttackMod;
                    sensorsAttackMod += ecmShieldAttackMod;
                    sensorsAttackMod += stealthAttackMod;
                }
                if (sensorsAttackMod > Mod.Config.Attack.NoSensorsPenalty)
                {
                    Mod.Log.Debug?.Write($"  Rollup of penalties {sensorsAttackMod} is > than NoSensors, defaulting to {Mod.Config.Attack.NoSensorsPenalty} ");
                    hasSensorAttack = false;
                }

                // Check firing blind
                if (!hasVisualAttack && !hasSensorAttack)
                {
                    string localText = new Localize.Text(Mod.Config.LocalizedText[ModConfig.LT_ATTACK_FIRING_BLIND]).ToString();
                    AddToolTipDetailMethod.GetValue(new object[] { localText, Mod.Config.Attack.FiringBlindPenalty });
                }
                else
                {
                    // Visual attacks
                    if (!hasVisualAttack)
                    {
                        string localText = new Localize.Text(Mod.Config.LocalizedText[ModConfig.LT_ATTACK_NO_VISUALS]).ToString();
                        AddToolTipDetailMethod.GetValue(new object[] { localText, Mod.Config.Attack.NoVisualsPenalty });
                    }
                    else
                    {
                        // If the zoom + mimetic is better than eyeball, use that. Otherwise, we're using the good ol mk.1 eyeball
                        if (zoomAttackMod < eyeballAttackMod)
                        {
                            string localText = new Localize.Text(Mod.Config.LocalizedText[ModConfig.LT_ATTACK_ZOOM_VISION]).ToString();
                            AddToolTipDetailMethod.GetValue(new object[] { localText, zoomVisionMod });
                        }

                        if (mimeticMod != 0)
                        {
                            string localText = new Localize.Text(Mod.Config.LocalizedText[ModConfig.LT_ATTACK_MIMETIC]).ToString();
                            AddToolTipDetailMethod.GetValue(new object[] { localText, mimeticMod });
                        }
                    }

                    if (attackerState.HasHeatVisionToTarget(__instance.DisplayedWeapon, magnitude))
                    {
                        int    heatAttackMod = attackerState.GetHeatVisionAttackMod(targetActor, magnitude, __instance.DisplayedWeapon);
                        string localText     = new Localize.Text(Mod.Config.LocalizedText[ModConfig.LT_ATTACK_HEAT_VISION]).ToString();
                        AddToolTipDetailMethod.GetValue(new object[] { localText, heatAttackMod });
                    }

                    if (!hasSensorAttack)
                    {
                        string localText = new Localize.Text(Mod.Config.LocalizedText[ModConfig.LT_ATTACK_NO_SENSORS]).ToString();
                        AddToolTipDetailMethod.GetValue(new object[] { localText, Mod.Config.Attack.NoSensorsPenalty });
                    }
                    else
                    {
                        if (ecmShieldAttackMod != 0)
                        {
                            string localText = new Localize.Text(Mod.Config.LocalizedText[ModConfig.LT_ATTACK_ECM_SHEILD]).ToString();
                            AddToolTipDetailMethod.GetValue(new object[] { localText, ecmShieldAttackMod });
                        }
                        if (narcAttackMod != 0)
                        {
                            string localText = new Localize.Text(Mod.Config.LocalizedText[ModConfig.LT_ATTACK_NARCED]).ToString();
                            AddToolTipDetailMethod.GetValue(new object[] { localText, narcAttackMod });
                        }
                        if (tagAttackMod != 0)
                        {
                            string localText = new Localize.Text(Mod.Config.LocalizedText[ModConfig.LT_ATTACK_TAGGED]).ToString();
                            AddToolTipDetailMethod.GetValue(new object[] { localText, tagAttackMod });
                        }
                        if (stealthAttackMod != 0)
                        {
                            string localText = new Localize.Text(Mod.Config.LocalizedText[ModConfig.LT_ATTACK_STEALTH]).ToString();
                            AddToolTipDetailMethod.GetValue(new object[] { localText, stealthAttackMod });
                        }
                    }
                }
            }
        }
        private static string BuildToolTip(AbstractActor actor)
        {
            //Mod.Log.Debug?.Write($"EW State for actor:{CombatantUtils.Label(actor)} = {ewState}");

            List <string> details = new List <string>();

            // Visuals check
            float visualLockRange = VisualLockHelper.GetVisualLockRange(actor);
            float visualScanRange = VisualLockHelper.GetVisualScanRange(actor);

            details.Add(
                new Text(Mod.LocalizedText.StatusPanel[ModText.LT_PANEL_VISUALS],
                         new object[] { visualLockRange, visualScanRange, ModState.GetMapConfig().UILabel() })
                .ToString()
                );

            // Sensors check
            EWState ewState = new EWState(actor);

            int            totalDetails = ewState.GetCurrentEWCheck() + ewState.AdvancedSensorsMod();
            SensorScanType checkLevel   = SensorScanTypeHelper.DetectionLevelForCheck(totalDetails);

            float rawRangeMulti = SensorLockHelper.GetAllSensorRangeMultipliers(actor);
            float rangeMulti    = rawRangeMulti + ewState.GetSensorsRangeMulti();

            float  sensorsRange = SensorLockHelper.GetSensorsRange(actor);
            string sensorColor  = ewState.GetCurrentEWCheck() >= 0 ? "00FF00" : "FF0000";

            details.Add(
                new Text(Mod.LocalizedText.StatusPanel[ModText.LT_PANEL_SENSORS],
                         new object[] { sensorColor, sensorsRange, sensorColor, rangeMulti, checkLevel.Label() })
                .ToString()
                );

            // Details
            //{ LT_PANEL_DETAILS, "  Total: <color=#{0}>{1:+0;-#}</color><size=90%> Roll: <color=#{2}>{3:+0;-#}</color> Tactics: <color=#00FF00>{4:+0;-#}</color> AdvSen: <color=#{5}>{6:+0;-#}</color>\n"
            string totalColor  = totalDetails >= 0 ? "00FF00" : "FF0000";
            string checkColor  = ewState.GetRawCheck() >= 0 ? "00FF00" : "FF0000";
            string advSenColor = ewState.AdvancedSensorsMod() >= 0 ? "00FF00" : "FF0000";

            details.Add(
                new Text(Mod.LocalizedText.StatusPanel[ModText.LT_PANEL_DETAILS],
                         new object[] { totalColor, totalDetails, checkColor, ewState.GetRawCheck(), ewState.GetRawTactics(), advSenColor, ewState.AdvancedSensorsMod() })
                .ToString()
                );

            //  Heat Vision
            if (ewState.GetRawHeatVision() != null)
            {
                // { LT_PANEL_HEAT, "<b>Thermals</b><size=90%> Mod:<color=#{0}>{1:+0;-#}</color> / {2} heat Range:{3}m\n" },
                HeatVision heatVis = ewState.GetRawHeatVision();
                // Positive is bad, negative is good
                string modColor = heatVis.AttackMod >= 0 ? "FF0000" : "00FF00";
                details.Add(
                    new Text(Mod.LocalizedText.StatusPanel[ModText.LT_PANEL_HEAT],
                             new object[] { modColor, heatVis.AttackMod, heatVis.HeatDivisor, heatVis.MaximumRange })
                    .ToString()
                    );
            }

            //  Zoom Vision
            if (ewState.GetRawZoomVision() != null)
            {
                // { LT_PANEL_ZOOM, "<b>Zoom</b><size=90%> Mod:<color=#{0}>{1:+0;-#}</color? Cap:<color=#{2}>{3:+0;-#}</color> Range:{4}m\n" },
                ZoomVision zoomVis = ewState.GetRawZoomVision();
                // Positive is bad, negative is good
                string modColor = zoomVis.AttackMod >= 0 ? "FF0000" : "00FF00";
                string capColor = zoomVis.AttackCap >= 0 ? "FF0000" : "00FF00";
                details.Add(
                    new Text(Mod.LocalizedText.StatusPanel[ModText.LT_PANEL_ZOOM],
                             new object[] { modColor, zoomVis.AttackMod, capColor, zoomVis.AttackCap, zoomVis.MaximumRange })
                    .ToString()
                    );
            }

            string tooltipText = String.Join("", details.ToArray());

            return(tooltipText);
        }