Example #1
0
        private void CalculateInstability(Mech attacker, AbstractActor target)
        {
            Mod.MeleeLog.Info?.Write($"Calculating DFA instability for attacker: {CombatantUtils.Label(attacker)} " +
                                     $"vs. target: {CombatantUtils.Label(target)}");

            float targetTonnage = 0;

            if (target is Vehicle vehicle)
            {
                targetTonnage = vehicle.tonnage;
            }
            else if (target is Mech mech)
            {
                targetTonnage = mech.tonnage;
            }
            Mod.MeleeLog.Info?.Write($" - Target tonnage is: {targetTonnage}");

            float attackerInstab = attacker.DFAAttackerInstability(targetTonnage);

            attackerInstab           = attacker.ApplyDFAInstabReduction(attackerInstab);
            this.AttackerInstability = attackerInstab;

            float targetInstab = attacker.DFATargetInstability();

            targetInstab           = target.ApplyDFAInstabReduction(targetInstab);
            this.TargetInstability = targetInstab;
        }
Example #2
0
        // Duplication of HBS code, avoiding prefix=true for now.
        public static void Postfix(ref BehaviorTreeResults __result, string ___name, BehaviorTree ___tree, AbstractActor ___unit)
        {
            Mod.Log.Info?.Write("CJMCN:T - entered");

            Mech mech = ___unit as Mech;

            if (mech != null && mech.WorkingJumpjets > 0)
            {
                string stayInsideRegionGUID = RegionUtil.GetStayInsideRegionGUID(___unit);

                float acceptableHeat = AIUtil.GetAcceptableHeatLevelForMech(mech);
                float currentHeat    = (float)mech.CurrentHeat;
                Mod.Log.Info?.Write($"CJMCN:T - === actor:{CombatantUtils.Label(mech)} has currentHeat:{currentHeat} and acceptableHeat:{acceptableHeat}");

                List <PathNode> sampledPathNodes = ___unit.JumpPathing.GetSampledPathNodes();
                Mod.Log.Info?.Write($"CJMCN:T - calculating {sampledPathNodes.Count} nodes");
                for (int i = 0; i < sampledPathNodes.Count; i++)
                {
                    Vector3 candidatePos      = sampledPathNodes[i].Position;
                    float   distanceBetween2D = AIUtil.Get2DDistanceBetweenVector3s(candidatePos, ___unit.CurrentPosition);
                    float   distanceBetween3D = Vector3.Distance(candidatePos, ___unit.CurrentPosition);
                    Mod.Log.Info?.Write($"CJMCN:T - calculated distances 2D:'{distanceBetween2D}' 3D:'{distanceBetween3D} ");
                    if (distanceBetween2D >= 1f)
                    {
                        float magnitude = (candidatePos - ___unit.CurrentPosition).magnitude;
                        float jumpHeat  = (float)mech.CalcJumpHeat(magnitude);
                        Mod.Log.Info?.Write($"CJMCN:T - calculated jumpHeat:'{jumpHeat}' from magnitude:'{magnitude}. ");

                        Mod.Log.Info?.Write($"CJMCN:T - comparing heat: [jumpHeat:'{jumpHeat}' + currentHeat:'{currentHeat}'] <= acceptableHeat:'{acceptableHeat}. ");
                        if (jumpHeat + (float)mech.CurrentHeat <= acceptableHeat)
                        {
                            if (stayInsideRegionGUID != null)
                            {
                                MapTerrainDataCell cellAt = ___unit.Combat.MapMetaData.GetCellAt(candidatePos);
                                if (cellAt != null)
                                {
                                    MapEncounterLayerDataCell mapEncounterLayerDataCell = cellAt.MapEncounterLayerDataCell;
                                    if (mapEncounterLayerDataCell != null &&
                                        mapEncounterLayerDataCell.regionGuidList != null &&
                                        !mapEncounterLayerDataCell.regionGuidList.Contains(stayInsideRegionGUID))
                                    {
                                        // Skip this loop iteration if
                                        Mod.Log.Info?.Write($"CJMCN:T - candidate outside of constraint region, ignoring.");
                                        goto CANDIDATE_OUTSIDE_REGION;
                                    }
                                }
                            }

                            Mod.Log.Info?.Write($"CJMCN:T - adding candidate position:{candidatePos}");
                            ___tree.movementCandidateLocations.Add(new MoveDestination(sampledPathNodes[i], MoveType.Jumping));
                        }
                    }

                    CANDIDATE_OUTSIDE_REGION :;
                }
            }

            // Should already be set by prefix method
            //__result = BehaviorTreeResults(BehaviorNodeState.Success);
        }
Example #3
0
        public static void Postfix(Vehicle __instance)
        {
            int initPhase = PhaseHelper.TonnageToPhase((int)Math.Ceiling(__instance.tonnage));

            Mod.Log.Debug?.Write($"Setting baseInit for vehicle: {CombatantUtils.Label(__instance)} to {initPhase}");
            __instance.StatCollection.Set <int>(ModStats.BaseInitiative, initPhase);
        }
        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;
                }
            }
        }
        // Iterates over all player-allied units, checks for highest lock level to determine unit
        public static SensorScanType CalculateSharedLock(ICombatant target, AbstractActor source)
        {
            SensorScanType scanType = SensorScanType.NoInfo;

            List <AbstractActor> sensorSources = new List <AbstractActor>();

            if (source == null)
            {
                // We don't have an active source, so treat the entire allied faction as sources
                Mod.Log.Trace?.Write($"No primary lock source, assuming all allies.");
                sensorSources.AddRange(target.Combat.GetAllAlliesOf(target.Combat.LocalPlayerTeam));
            }
            else
            {
                // We have an active source, so only use that model plus any 'shares sensors' models
                Mod.Log.Trace?.Write($"Actor:({CombatantUtils.Label(source)}) is primary lock source.");
                sensorSources.Add(source);
            }

            Mod.Log.Trace?.Write($"Checking locks from {sensorSources.Count} sources.");
            foreach (AbstractActor actor in sensorSources)
            {
                SensorScanType actorScanType = CalculateSensorInfoLevel(actor, target);
                if (actorScanType > scanType)
                {
                    Mod.Log.Trace?.Write($"Increasing scanType to: ({actorScanType}) from source:({CombatantUtils.Label(actor)}) ");
                    scanType = actorScanType;
                }
            }

            Mod.Log.Trace?.Write($"Shared lock to target:({CombatantUtils.Label(target)}) is type: ({scanType})");
            return(scanType);
        }
Example #6
0
        public static void Team_AddUnit_Postfix(Team __instance, AbstractActor unit)
        {
            // The added unit is a reinforcement if round > 0
            if (__instance.Combat.TurnDirector.CurrentRound > 1 && Mod.Config.Combat.SpawnProtection.ApplyToReinforcements)
            {
                HostilityMatrix hm = __instance.Combat.HostilityMatrix;

                Mod.Log.Info($"Checking actor:{CombatantUtils.Label(unit)} that belongs to team:{unit?.team?.Name}");

                List <AbstractActor> actor = new List <AbstractActor>();
                if (hm.IsLocalPlayerEnemy(unit.TeamId) && Mod.Config.Combat.SpawnProtection.ApplyToEnemies)
                {
                    actor.Add(unit);
                }
                else if (hm.IsLocalPlayerNeutral(unit.TeamId) && Mod.Config.Combat.SpawnProtection.ApplyToNeutrals)
                {
                    actor.Add(unit);
                }
                else if (hm.IsLocalPlayerFriendly(unit.TeamId) && Mod.Config.Combat.SpawnProtection.ApplyToAllies)
                {
                    actor.Add(unit);
                }

                if (actor.Count == 1)
                {
                    Mod.Log.Info($"Applying protection to reinforcement:{CombatantUtils.Label(unit)}");
                    ProtectionHelper.ProtectActors(actor);
                }
            }
        }
        private static void Prefix(Team __instance, List <IStackSequence> __result)
        {
            Mod.Log.Debug?.Write($"T:DWAAA invoked");
            if (!__instance.IsLocalPlayer)
            {
                return;
            }

            if (__instance.Combat.TurnDirector.IsInterleavePending)
            {
                if (__result == null)
                {
                    Mod.Log.Debug?.Write("Result was null, adding a new list.");
                    __result = new List <IStackSequence>();
                }

                int numUnitsEndingActivation = 0;
                foreach (AbstractActor unit in __instance.units)
                {
                    Mod.Log.Debug?.Write($"Processing unit: {unit.DisplayName}_{unit.GetPilot().Name}");
                    if (!unit.IsCompletingActivation && !unit.IsDead && !unit.IsFlaggedForDeath)
                    {
                        Mod.Log.Info?.Write($"  Ending activation for unit {CombatantUtils.Label(unit)}");
                        IStackSequence item = unit.DoneWithActor();
                        numUnitsEndingActivation++;
                        __result.Add(item);
                    }
                }

                Traverse numUnitsEndingActivationT = Traverse.Create(__instance).Field("numUnitsEndingActivation");
                int      currentValue = numUnitsEndingActivationT.GetValue <int>();
                numUnitsEndingActivationT.SetValue(currentValue + numUnitsEndingActivation);
            }
        }
Example #8
0
        public static void ProtectActor(AbstractActor actor)
        {
            if (actor is Turret turret)
            {
                Mod.Log.Info?.Write($" Spawn protection skipping turret:{CombatantUtils.Label(actor)}");
                return;
            }

            if (!ModState.SpawnProtectedUnits.Contains(actor.GUID))
            {
                Mod.Log.Info?.Write($" Spawn protecting actor: {actor.DistinctId()}");
                if (Mod.Config.Combat.SpawnProtection.ApplyGuard)
                {
                    Mod.Log.Info?.Write($" -- applying Braced state");
                    actor.ApplyBraced();
                }

                if (Mod.Config.Combat.SpawnProtection.EvasionPips > 0)
                {
                    Mod.Log.Info?.Write($" -- setting evasion pips to: {Mod.Config.Combat.SpawnProtection.EvasionPips}");
                    actor.EvasivePipsCurrent = Mod.Config.Combat.SpawnProtection.EvasionPips;
                    Traverse.Create(actor).Property("EvasivePipsTotal").SetValue(actor.EvasivePipsCurrent);
                    //AccessTools.Property(typeof(AbstractActor), "EvasivePipsTotal").SetValue(actor, actor.EvasivePipsCurrent, null);
                    SharedState.Combat.MessageCenter.PublishMessage(new EvasiveChangedMessage(actor.GUID, actor.EvasivePipsCurrent));
                }

                ModState.SpawnProtectedUnits.Add(actor.GUID);
            }
            else
            {
                Mod.Log.Info?.Write($"Actor: {actor.DistinctId()} already protected, skipping.");
            }
        }
Example #9
0
        public static List <BattleTech.Building> ClosestCandidatesToPosition(Vector3 originPos, float searchRadius)
        {
            List <BattleTech.Building> candidates = new List <BattleTech.Building>();

            foreach (BattleTech.Building building in ModState.CandidateBuildings)
            {
                if (!building.IsDead && (building.CurrentPosition - originPos).magnitude < searchRadius)
                {
                    Mod.Log.Debug?.Write($" -- Candidate building: {CombatantUtils.Label(building)} at position: {building.CurrentPosition} is within search range.");
                    candidates.Add(building);
                }
                else
                {
                    Mod.Log.Trace?.Write($" -- Candidate building: {CombatantUtils.Label(building)} at position: {building.CurrentPosition} is beyond search range");
                }
            }

            // Remove any candidates that already have a trap in them
            candidates.RemoveAll(x => ModState.AmbushBuildingGUIDToTurrets.ContainsKey(x.GUID));

            // Sort candidates by distance from the origin
            candidates.Sort((b1, b2) =>
                            (b1.CurrentPosition - originPos).magnitude.CompareTo((b2.CurrentPosition - originPos).magnitude)
                            );

            return(candidates);
        }
Example #10
0
        public static void Postfix(AbstractActor __instance, ref int __result, StatCollection ___statCollection)
        {
            Mod.Log.Trace?.Write("AA:BI - entered.");

            Mod.Log.Debug?.Write($"Actor:({CombatantUtils.Label(__instance)}) has raw result: {__result}");
            if (__instance.Combat.TurnDirector.IsInterleaved)
            {
                int baseInit     = ___statCollection.GetValue <int>("BaseInitiative");
                int phaseMod     = ___statCollection.GetValue <int>("PhaseModifier");
                int modifiedInit = baseInit + phaseMod;

                if (modifiedInit < Mod.MinPhase)
                {
                    Mod.Log.Info?.Write($"Actor:({CombatantUtils.Label(__instance)}) being set to {Mod.MinPhase} due to BaseInit:{baseInit} + PhaseMod:{phaseMod}");
                    __result = Mod.MinPhase;
                }
                else if (modifiedInit > Mod.MaxPhase)
                {
                    Mod.Log.Info?.Write($"Actor:({CombatantUtils.Label(__instance)}) being set to {Mod.MaxPhase} due to BaseInit:{baseInit} + PhaseMod:{phaseMod}");
                    __result = Mod.MaxPhase;
                }
                else
                {
                    Mod.Log.Info?.Write($"Actor:({CombatantUtils.Label(__instance)}) has stats BaseInit:{baseInit} + PhaseMod:{phaseMod} = modifiedInit:{modifiedInit}.");
                    __result = modifiedInit;
                }
            }
            else
            {
                Mod.Log.Info?.Write($"Actor:({CombatantUtils.Label(__instance)}) is non-interleaved, returning phase: {Mod.MaxPhase}.");
                __result = Mod.MaxPhase;
            }
        }
Example #11
0
        public static void DevestateBuildings()
        {
            if (!Mod.Config.Devastation.Enabled)
            {
                return;
            }

            Mod.Log.Debug?.Write("Processing buildings for pre-battle devestation.");

            List <BattleTech.Building> shuffledBuildings = new List <BattleTech.Building>();

            shuffledBuildings.AddRange(ModState.CandidateBuildings);

            // Randomize the buildings by shuffling them
            shuffledBuildings.Shuffle();

            int   minNum             = (int)(Mod.Config.Devastation.DefaultRange.MinDevastation * 100f);
            int   maxNum             = (int)(Mod.Config.Devastation.DefaultRange.MaxDevastation * 100f);
            int   destroyPercentile  = Mod.Random.Next(minNum, maxNum);
            float destroyPercent     = (float)destroyPercentile / 100f;
            int   destroyedBuildings = (int)Math.Floor(shuffledBuildings.Count * destroyPercent);

            Mod.Log.Debug?.Write($"Destruction percentile: {destroyPercent} applied to {shuffledBuildings.Count} buildings = {destroyedBuildings} destroyed buildings.");

            for (int i = 0; i < destroyedBuildings; i++)
            {
                BattleTech.Building building = shuffledBuildings.ElementAt(i);
                Mod.Log.Debug?.Write($"Destroying building: {CombatantUtils.Label(building)}");

                building.FlagForDeath("CG_PREMAP_DESTROY", DeathMethod.DespawnedNoMessage, DamageType.NOT_SET, 1, -1, "0", true);
                building.HandleDeath("0");
                ModState.CandidateBuildings.Remove(building);
            }
        }
            private static void Prefix(MechJumpSequence __instance)
            {
                Mod.ActivationLog.Debug?.Write($"MJS:OC entered for actor: {CombatantUtils.Label(__instance.OwningMech)}");

                // Check for visibility to any enemies
                if (!__instance.owningActor.Combat.TurnDirector.IsInterleaved)
                {
                    Mod.ActivationLog.Info?.Write("MJS:OC is not interleaved and no enemies - autobracing ");
                    __instance.owningActor.AutoBrace = true;
                }

                Mod.Log.Trace?.Write($"JUMP -- ABILITY_CONSUMES_FIRING: {__instance.AbilityConsumesFiring} / CONSUMES_FIRING: {__instance.ConsumesFiring}");

                if (__instance.OwningMech == null)
                {
                    return;                                // Nothing more to do
                }
                // Movement - check for damage after a jump, and if so force a piloting check
                if (__instance.OwningMech.ActuatorDamageMalus() != 0 || Mod.Config.Developer.ForceFallAfterJump)
                {
                    Mod.Log.Info?.Write($"Actor: {CombatantUtils.Label(__instance.OwningMech)} has actuator damage, forcing piloting check.");
                    float sourceSkillMulti = __instance.OwningMech.PilotCheckMod(Mod.Config.SkillChecks.ModPerPointOfPiloting);
                    float damagePenalty    = __instance.OwningMech.ActuatorDamageMalus() * Mod.Config.SkillChecks.ModPerPointOfPiloting;
                    float checkMod         = sourceSkillMulti + damagePenalty;
                    Mod.Log.Debug?.Write($"  moveSkillMulti:{sourceSkillMulti} - damagePenalty: {damagePenalty} = checkMod: {checkMod}");

                    bool sourcePassed = Mod.Config.Developer.ForceFallAfterJump ? false :
                                        CheckHelper.DidCheckPassThreshold(Mod.Config.Move.FallAfterJumpChance, __instance.OwningMech, checkMod, ModText.FT_Fall_After_Jump);
                    if (!sourcePassed)
                    {
                        Mod.Log.Info?.Write($"Source actor: {CombatantUtils.Label(__instance.OwningMech)} failed pilot check after jumping with actuator damage, forcing fall.");
                        MechHelper.AddFallingSequence(__instance.OwningMech, __instance, ModText.FT_Fall_After_Jump);
                    }
                }
            }
            private static void Prefix(ActorMovementSequence __instance)
            {
                Mod.ActivationLog.Info?.Write($"AMS:OC:PRE entered for actor: {CombatantUtils.Label(__instance?.OwningActor)}");

                // Interleaved - check for visibility to any enemies
                if (!__instance.owningActor.Combat.TurnDirector.IsInterleaved)
                {
                    __instance.owningActor.AutoBrace = true;
                }

                // Movement - check for damage after a sprint, and if so force a piloting check
                if (__instance.OwningMech != null && __instance.isSprinting && __instance.OwningMech.ActuatorDamageMalus() != 0)
                {
                    Mod.Log.Info?.Write($"Actor: {CombatantUtils.Label(__instance.OwningMech)} has actuator damage, forcing piloting check.");
                    float sourceSkillMulti = __instance.OwningMech.PilotCheckMod(Mod.Config.SkillChecks.ModPerPointOfPiloting);
                    float damagePenalty    = __instance.OwningMech.ActuatorDamageMalus() * Mod.Config.SkillChecks.ModPerPointOfPiloting;
                    float checkMod         = sourceSkillMulti + damagePenalty;
                    Mod.Log.Debug?.Write($"  moveSkillMulti:{sourceSkillMulti} - damagePenalty: {damagePenalty} = checkMod: {checkMod}");

                    bool sourcePassed = CheckHelper.DidCheckPassThreshold(Mod.Config.Move.FallAfterRunChance, __instance.OwningMech, checkMod, ModText.FT_Fall_After_Run);
                    if (!sourcePassed)
                    {
                        Mod.Log.Info?.Write($"Source actor: {CombatantUtils.Label(__instance.OwningMech)} failed pilot check after sprinting with actuator damage, forcing fall.");
                        MechHelper.AddFallingSequence(__instance.OwningMech, __instance, ModText.FT_Fall_After_Run);
                    }
                }
            }
Example #14
0
        // Per BT Manual pg.36,
        //   * target takes 1 pt. each 10 tons of attacker, which is then multiplied by 3 and rounded up
        //   * attacker takes tonnage / 5, rounded up
        //   * Damage clustered in 5 (25) point groupings for both attacker & defender
        //   * Target resolves on punch table
        //   *   Prone targets resolve on rear table
        //   * Attacker resolves on kick table
        //   * Comparative attack modifier; difference in attacker and defender is applied to attack
        //   *  +3 modifier to hit for jumping
        //   *  +2 to hit if upper or lower leg actuators are hit
        //   *  -2 modifier if target is prone
        //   * Attacker makes PSR with +4, target with +2 and fall

        public DFAAttack(MeleeState state) : base(state)
        {
            Mod.MeleeLog.Info?.Write($"Building DFA state for attacker: {CombatantUtils.Label(state.attacker)} @ attackPos: {state.attackPos} vs. target: {CombatantUtils.Label(state.target)}");

            this.Label   = Mod.LocalizedText.Labels[ModText.LT_Label_Melee_Type_DeathFromAbove];
            this.IsValid = ValidateAttack(state.attacker, state.target);
            if (IsValid)
            {
                this.UsePilotingDelta = Mod.Config.Melee.DFA.UsePilotingDelta;

                CalculateDamages(state.attacker, state.target);
                CalculateInstability(state.attacker, state.target);
                CalculateModifiers(state.attacker, state.target);
                CreateDescriptions(state.attacker, state.target);

                // Damage tables
                this.AttackerTable = DamageTable.KICK;
                this.TargetTable   = state.target.IsProne ? DamageTable.REAR : DamageTable.PUNCH;

                // Unsteady
                this.UnsteadyAttackerOnHit  = Mod.Config.Melee.DFA.UnsteadyAttackerOnHit;
                this.UnsteadyAttackerOnMiss = Mod.Config.Melee.DFA.UnsteadyAttackerOnMiss;

                this.OnTargetMechHitForceUnsteady         = Mod.Config.Melee.DFA.UnsteadyTargetOnHit;
                this.OnTargetVehicleHitEvasionPipsRemoved = Mod.Config.Melee.DFA.TargetVehicleEvasionPipsRemoved;

                // Set the animation type
                this.AttackAnimation = MeleeAttackType.DFA;
            }
        }
        public static bool ResolvePilotInjuryCheck(Mech mech, int heatToCheck, int rootSequenceGUID, int sequenceGUID, float heatCheck)
        {
            bool failedInjuryCheck = !CheckHelper.DidCheckPassThreshold(Mod.Config.Heat.PilotInjury, heatToCheck, mech, heatCheck, ModText.FT_Check_Injury);

            Mod.Log.Debug?.Write($"  failedInjuryCheck: {failedInjuryCheck}");
            if (failedInjuryCheck)
            {
                Mod.Log.Info?.Write($"-- Pilot Heat Injury check failed for {CombatantUtils.Label(mech)}, forcing injury from heat");
                mech.pilot.InjurePilot(sequenceGUID.ToString(), rootSequenceGUID, 1, DamageType.OverheatSelf, null, mech);
                if (!mech.pilot.IsIncapacitated)
                {
                    AudioEventManager.SetPilotVOSwitch <AudioSwitch_dialog_dark_light>(AudioSwitch_dialog_dark_light.dark, mech);
                    AudioEventManager.PlayPilotVO(VOEvents.Pilot_TakeDamage, mech, null, null, true);
                    if (mech.team.LocalPlayerControlsTeam)
                    {
                        AudioEventManager.PlayAudioEvent("audioeventdef_musictriggers_combat", "friendly_warrior_injured", null, null);
                    }
                }
                else
                {
                    mech.FlagForDeath("Pilot Killed", DeathMethod.PilotKilled, DamageType.OverheatSelf, 1, sequenceGUID, "0", false);
                    string localText = new Text(Mod.LocalizedText.Floaties[ModText.FT_Death_By_Overheat]).ToString();

                    mech.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage(
                                                                 new ShowActorInfoSequence(mech, new Text(localText, Array.Empty <object>()), FloatieMessage.MessageNature.PilotInjury, true))
                                                             );
                    mech.HandleDeath("0");
                }
            }

            return(failedInjuryCheck);
        }
Example #16
0
        // WARNING: DUPLICATE OF HBS CODE. THIS IS LIKELY TO BREAK IF HBS CHANGES THE SOURCE FUNCTIONS
        public static float GetAdjustedSpotterRange(AbstractActor source, ICombatant target)
        {
            float         targetVisibility = 1f;
            AbstractActor targetActor      = target as AbstractActor;

            if (targetActor != null)
            {
                EWState sourceState = source.GetEWState();
                targetVisibility = VisualLockHelper.GetTargetVisibility(targetActor, sourceState);
            }

            float spotterRange = VisualLockHelper.GetSpotterRange(source);

            float modifiedRange = spotterRange * targetVisibility;

            if (modifiedRange < Mod.Config.Vision.MinimumVisionRange())
            {
                modifiedRange = Mod.Config.Vision.MinimumVisionRange();
            }

            // Round up to the nearest full hex
            float normalizedRange = HexUtils.CountHexes(modifiedRange, true) * 30f;

            Mod.Log.Trace?.Write($" -- source:{CombatantUtils.Label(source)} adjusted spotterRange:{normalizedRange}m normalized from:{modifiedRange}m");
            return(normalizedRange);
        }
Example #17
0
        // Prevent call to checkForHeatDamage
        private void setState(EmergencyShutdownState newState)
        {
            Mod.Log.Info?.Write($"MESS - Setting state to: {newState} for actor: {CombatantUtils.Label(this.OwningMech)}");
            if (this.state == newState)
            {
                return;
            }
            this.state = newState;
            this.timeInCurrentState = 0f;
            if (newState == EmergencyShutdownState.ShuttingDown)
            {
                if (this.OwningMech.GameRep != null)
                {
                    this.OwningMech.GameRep.PlayShutdownAnim();
                }
                this.OwningMech.CancelCreatedEffects();
                return;
            }
            if (newState != EmergencyShutdownState.Finished)
            {
                return;
            }

            Mech.heatLogger.Log("Mech " + this.OwningMech.DisplayName + " shuts down from overheating");
            this.OwningMech.IsShutDown = true;
            this.OwningMech.DumpAllEvasivePips();
        }
            private static void Prefix(ActorMovementSequence __instance)
            {
                Mod.Log.Trace("AMS:OC entered");
                // Interleaved - check for visibility to any enemies
                if (!__instance.owningActor.Combat.TurnDirector.IsInterleaved)
                {
                    if (__instance.owningActor.Combat.LocalPlayerTeam.GetDetectedEnemyUnits().Count > 0)
                    {
                        Mod.Log.Info("AMS:OC TD is not interleaved but enemies are detected - disabling autobrace. ");
                        __instance.owningActor.AutoBrace = false;
                    }
                    else
                    {
                        Mod.Log.Info("AMS:OC TD is not interleaved and no enemies - autobracing ");
                        __instance.owningActor.AutoBrace = true;
                    }
                }

                // Movement - check for damage after a sprint, and if so force a piloting check
                if (__instance.OwningMech != null && __instance.isSprinting && __instance.OwningMech.ActuatorDamageMalus() != 0)
                {
                    Mod.Log.Debug($"Actor: {CombatantUtils.Label(__instance.OwningMech)} has actuator damage, forcing piloting check.");
                    float sourceSkillMulti = __instance.OwningMech.PilotCheckMod(Mod.Config.Move.SkillMulti);
                    bool  sourcePassed     = CheckHelper.DidCheckPassThreshold(Mod.Config.Move.FallAfterRunChance, __instance.OwningMech, sourceSkillMulti, ModConfig.FT_Fall_After_Run);
                    if (!sourcePassed)
                    {
                        Mod.Log.Info($"Source actor: {CombatantUtils.Label(__instance.OwningMech)} failed pilot check after sprinting with actuator damage, forcing fall.");
                        MechHelper.AddFallingSequence(__instance.OwningMech, __instance, ModConfig.FT_Fall_After_Run);
                    }
                }
            }
Example #19
0
        // This assumes you're calling from a place that has already determined that we can reach the target.
        public static MeleeState GetMeleeStates(AbstractActor attacker, Vector3 attackPos, ICombatant target)
        {
            if (attacker == null || target == null)
            {
                Mod.MeleeLog.Warn?.Write("Null attacker or target - cannot melee!");
                return(new MeleeState());
            }

            Mech attackerMech = attacker as Mech;

            if (attackerMech == null)
            {
                Mod.MeleeLog.Warn?.Write("Vehicles and buildings cannot melee!");
                return(new MeleeState());
            }

            AbstractActor targetActor = target as AbstractActor;

            if (targetActor == null)
            {
                Mod.MeleeLog.Error?.Write("Target is not an abstractactor - must be building. Cannot melee!");
                return(new MeleeState());
            }

            Mod.MeleeLog.Info?.Write($"Building melee state for attacker: {CombatantUtils.Label(attacker)} against target: {CombatantUtils.Label(target)}");

            MeleeState states = new MeleeState(attackerMech, attackPos, targetActor);

            Mod.MeleeLog.Info?.Write($" - valid attacks => charge: {states.Charge.IsValid}  dfa: {states.DFA.IsValid}  kick: {states.Kick.IsValid}  " +
                                     $"weapon: {states.PhysicalWeapon.IsValid}  punch: {states.Punch.IsValid}");

            return(states);
        }
        public static void Postfix(CombatHUDTargetingComputer __instance, List <LocalizableText> ___weaponNames, UIManager ___uiManager)
        {
            // Skip processing if we're not initialized properly
            if (__instance == null || __instance?.ActivelyShownCombatant?.Combat == null || __instance.WeaponList == null)
            {
                return;
            }

            if (__instance.ActivelyShownCombatant is BattleTech.Building building && ModState.AmbushBuildingGUIDToTurrets.ContainsKey(building.GUID))
            {
                Mod.Log.Trace?.Write($"TargetingHUD target {CombatantUtils.Label(__instance.ActivelyShownCombatant)} is trapped enemy building");

                // Replicate RefreshWeaponList here as it expects an AbstractActor
                Turret turret = ModState.AmbushBuildingGUIDToTurrets[building.GUID];
                for (int i = 0; i < ___weaponNames.Count; i++)
                {
                    if (turret != null && i < turret.Weapons.Count)
                    {
                        Weapon weapon = turret.Weapons[i];
                        ___weaponNames[i].SetText(weapon.UIName);
                        if (!weapon.HasAmmo)
                        {
                            ___weaponNames[i].color = ___uiManager.UIColorRefs.qualityD;
                        }
                        else
                        {
                            switch (weapon.DamageLevel)
                            {
                            case ComponentDamageLevel.Functional:
                                ___weaponNames[i].color = ___uiManager.UIColorRefs.qualityA;
                                break;

                            case ComponentDamageLevel.Misaligned:
                            case ComponentDamageLevel.Penalized:
                                ___weaponNames[i].color = ___uiManager.UIColorRefs.structureUndamaged;
                                break;

                            case ComponentDamageLevel.NonFunctional:
                            case ComponentDamageLevel.Destroyed:
                                ___weaponNames[i].color = ___uiManager.UIColorRefs.qualityD;
                                break;
                            }
                        }
                        if (!___weaponNames[i].transform.parent.gameObject.activeSelf)
                        {
                            ___weaponNames[i].transform.parent.gameObject.SetActive(true);
                        }
                    }
                    else if (___weaponNames[i].transform.parent.gameObject.activeSelf)
                    {
                        ___weaponNames[i].transform.parent.gameObject.SetActive(false);
                    }
                }

                __instance.WeaponList.SetActive(true);
                Transform  weaponListT  = __instance.WeaponList?.transform?.parent?.Find("tgtWeaponsLabel");
                GameObject weaponsLabel = weaponListT.gameObject;
                weaponsLabel.SetActive(true);
            }
        }
Example #21
0
        public float ECMSignatureMod(EWState attackerState)
        {
            if (shieldedByECMMod <= 0)
            {
                return(0f);
            }

            int strength = shieldedByECMMod - attackerState.ProbeCarrierMod();

            if (this.PingedByProbeMod() > 0)
            {
                strength -= this.PingedByProbeMod();
            }
            if (attackerState.ProbeCarrierMod() > 0)
            {
                strength -= attackerState.ProbeCarrierMod();
            }

            // Probe can reduce you to zero, but not further.
            strength = Math.Max(0, strength);

            float sigMod = strength * 0.1f;

            if (sigMod != 0)
            {
                Mod.Log.Trace?.Write($"Target:({CombatantUtils.Label(actor)}) has ECMSignatureMod:{sigMod}");
            }

            return(sigMod);
        }
        static void Postfix(AbstractActor __instance)
        {
            // TODO: Allow spawn on ally as well
            if (ModState.IsUrbanBiome &&
                __instance.team.IsLocalPlayer &&
                !__instance.Combat.TurnDirector.IsInterleaved &&
                !__instance.Combat.TurnDirector.IsInterleavePending)
            {
                // Check that we haven't exhausted the max traps for this mission
                if (ModState.Ambushes >= Mod.Config.Ambush.MaxPerMap)
                {
                    return;
                }

                // Validate that we are far enough away from trap origins to spawn another
                foreach (Vector3 ambushOrigin in ModState.AmbushOrigins)
                {
                    float magnitude = (__instance.CurrentPosition - ambushOrigin).magnitude;
                    if (magnitude < Mod.Config.Ambush.MinDistanceBetween)
                    {
                        Mod.Log.Debug?.Write($" Actor {CombatantUtils.Label(__instance)} at pos: {__instance.CurrentPosition} is {magnitude}m away from " +
                                             $"previous trap origin: {ambushOrigin}. Skipping.");
                        return;
                    }
                }

                // If we've made it this far, record it as a potential ambush site
                ModState.PotentialAmbushOrigins.Add(__instance.CurrentPosition);
            }
        }
            public static void Postfix(CombatHUDStatusPanel __instance, Mech mech)
            {
                Mod.Log.Trace("CHUBSP:SSDI:POST entered.");

                // TODO: FIXME
                var        type       = __instance.GetType();
                MethodInfo methodInfo = type.GetMethod("ShowDebuff", (BindingFlags.NonPublic | BindingFlags.Instance), null,
                                                       new Type[] { typeof(SVGAsset), typeof(Text), typeof(Text), typeof(Vector3), typeof(bool) }, new ParameterModifier[5]);

                if (mech.IsShutDown)
                {
                    Mod.Log.Debug($"Mech:{CombatantUtils.Label(mech)} is shutdown.");
                    methodInfo.Invoke(__instance, new object[] { LazySingletonBehavior <UIManager> .Instance.UILookAndColorConstants.StatusShutDownIcon,
                                                                 new Text("SHUT DOWN", new object[0]), new Text("This target is easier to hit, and Called Shots can be made against this target.", new object[0]),
                                                                 __instance.defaultIconScale, false });
                }
                else if (mech.IsOverheated)
                {
                    float shutdownChance      = 0;
                    float ammoExplosionChance = 0;
                    // FIXME: Remove this old code
                    Mod.Log.Debug($"Mech:{CombatantUtils.Label(mech)} is overheated, shutdownChance:{shutdownChance}% ammoExplosionChance:{ammoExplosionChance}%");

                    string descr = string.Format("This unit may trigger a Shutdown at the end of the turn unless heat falls below critical levels." +
                                                 "\nShutdown Chance: {0:P2}\nAmmo Explosion Chance: {1:P2}",
                                                 shutdownChance, ammoExplosionChance);
                    methodInfo.Invoke(__instance, new object[] { LazySingletonBehavior <UIManager> .Instance.UILookAndColorConstants.StatusOverheatingIcon,
                                                                 new Text("OVERHEATING", new object[0]), new Text(descr, new object[0]), __instance.defaultIconScale, false });
                }
            }
Example #24
0
        private void CalculateDamages(Mech attacker, AbstractActor target, int hexesMoved)
        {
            Mod.MeleeLog.Info?.Write($"Calculating CHARGE damage for attacker: {CombatantUtils.Label(attacker)} " +
                                     $"vs. target: {CombatantUtils.Label(target)} at hexesMoved: {hexesMoved}");

            float targetTonnage = 0;

            if (target is Vehicle vehicle)
            {
                targetTonnage = vehicle.tonnage;
            }
            else if (target is Mech mech)
            {
                targetTonnage = mech.tonnage;
            }
            else if (target is Turret turret)
            {
                targetTonnage = turret.MeleeTonnage();
            }
            Mod.MeleeLog.Info?.Write($" - Tonnage => Attacker: {attacker.tonnage}  Target: {targetTonnage}");

            // Calculate attacker damage
            float attackerDamage = attacker.ChargeAttackerDamage(targetTonnage);

            attackerDamage = attacker.ApplyChargeDamageReduction(attackerDamage);
            DamageHelper.ClusterDamage(attackerDamage, Mod.Config.Melee.Charge.DamageClusterDivisor, out this.AttackerDamageClusters);

            // Resolve target damage - include movement!
            float targetDamage = attacker.ChargeTargetDamage(hexesMoved);

            targetDamage = target.ApplyChargeDamageReduction(targetDamage);
            DamageHelper.ClusterDamage(targetDamage, Mod.Config.Melee.Charge.DamageClusterDivisor, out this.TargetDamageClusters);
        }
Example #25
0
        // WARNING: DUPLICATE OF HBS CODE. THIS IS LIKELY TO BREAK IF HBS CHANGES THE SOURCE FUNCTIONS
        public static float GetSensorsRange(AbstractActor source)
        {
            if (source.StatCollection.ContainsStatistic(ModStats.DisableSensors))
            {
                Mod.Log.Debug?.Write($"Returning minimum sensors range for {CombatantUtils.Label(source)} due to disabled sensors.");
                return(Mod.Config.Sensors.MinimumSensorRange());
            }

            // Add multipliers and absolute bonuses
            EWState ewState = source.GetEWState();

            Mod.Log.Trace?.Write($"  == Sensors Range for for actor:{CombatantUtils.Label(source)}");

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

            Mod.Log.Trace?.Write($"    rangeMulti: {rangeMulti} = rawRangeMulti: {rawRangeMulti} + sensorCheckRangeMulti: {ewState.GetSensorsRangeMulti()}");

            float rawRangeMod = SensorLockHelper.GetAllSensorRangeAbsolutes(source);
            float rangeMod    = rawRangeMod * (1 + ewState.GetSensorsRangeMulti());

            Mod.Log.Trace?.Write($"    rangeMod: {rangeMod} = rawRangeMod: {rawRangeMod} + sensorCheckRangeMulti: {ewState.GetSensorsRangeMulti()}");

            float sensorsRange = ewState.GetSensorsBaseRange() * rangeMulti + rangeMod;

            Mod.Log.Trace?.Write($"    sensorsRange: { sensorsRange} = baseRange: {ewState.GetSensorsBaseRange()} * rangeMult: {rangeMulti} + rangeMod: {rangeMod}");

            if (sensorsRange < Mod.Config.Sensors.MinimumSensorRange())
            {
                sensorsRange = Mod.Config.Sensors.MinimumSensorRange();
            }

            return(sensorsRange);
        }
Example #26
0
        private void CalculateInstability(Mech attacker, AbstractActor target, int hexesMoved)
        {
            Mod.MeleeLog.Info?.Write($"Calculating CHARGE instability for attacker: {CombatantUtils.Label(attacker)} " +
                                     $"vs. target: {CombatantUtils.Label(target)} at hexesMoved: {hexesMoved}");

            float targetTonnage = 0;

            if (target is Vehicle vehicle)
            {
                targetTonnage = vehicle.tonnage;
            }
            else if (target is Mech mech)
            {
                targetTonnage = mech.tonnage;
            }
            Mod.MeleeLog.Info?.Write($" - Target tonnage is: {targetTonnage}");

            // Resolve attacker instability
            float attackerInstab = attacker.ChargeAttackerInstability(targetTonnage, hexesMoved);

            attackerInstab           = attacker.ApplyChargeInstabReduction(attackerInstab);
            this.AttackerInstability = attackerInstab;

            // Resolve target instability
            float targetInstab = attacker.ChargeTargetInstability(targetTonnage, hexesMoved);

            targetInstab           = target.ApplyChargeInstabReduction(targetInstab);
            this.TargetInstability = targetInstab;
        }
        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);
        }
Example #28
0
        public static void Prefix(AbstractActor __instance)
        {
            Mod.Log.Debug($" OnMoveComplete for Actor: {CombatantUtils.Label(__instance)}");
            foreach (KeyValuePair <string, Statistic> kvp in __instance.StatCollection)
            {
                if (kvp.Key.EndsWith("_AH_SOURCES") || kvp.Key.EndsWith("_AH_VALUES"))
                {
                    Mod.Log.Debug($" -- stat: ({kvp.Key}) has value: ({kvp.Value.Value<string>()})");
                }
            }

            foreach (AbstractActor unit in __instance.team.units)
            {
                if (unit.GUID != __instance.GUID)
                {
                    Mod.Log.Debug($" -- friendly actor: {CombatantUtils.Label(unit)}");
                    foreach (KeyValuePair <string, Statistic> kvp in unit.StatCollection)
                    {
                        if (kvp.Key.EndsWith("_AH_SOURCES") || kvp.Key.EndsWith("_AH_VALUES"))
                        {
                            Mod.Log.Debug($" -- stat: ({kvp.Key}) has value: ({kvp.Value.Value<string>()})");
                        }
                    }
                }
            }
        }
        public static void Postfix(AbstractActor __instance, Effect effect)
        {
            Mod.Log.Trace?.Write("AA:CancelEffect entered");

            if (effect.EffectData.effectType == EffectType.StatisticEffect)
            {
                Mod.Log.Debug?.Write($" Cancelling effectId: '{effect.EffectData.Description.Id}'  effectName: '{effect.EffectData.Description.Name}'  " +
                                     $"on actor: '{CombatantUtils.Label(__instance)}'  from creator: {effect.creatorID}");

                if (effect.EffectData.effectType == EffectType.StatisticEffect && ModStats.IsStealthStat(effect.EffectData.statisticData.statName))
                {
                    Mod.Log.Debug?.Write("  - Stealth effect found, rebuilding visibility.");
                    List <ICombatant> allLivingCombatants = __instance.Combat.GetAllLivingCombatants();
                    __instance.VisibilityCache.UpdateCacheReciprocal(allLivingCombatants);

                    // TODO: Set current stealth pips?
                }

                if (ModStats.IsStealthStat(effect.EffectData.statisticData.statName))
                {
                    Mod.Log.Debug?.Write("  - Stealth effect found, rebuilding visibility.");
                    List <ICombatant> allLivingCombatants = __instance.Combat.GetAllLivingCombatants();
                    __instance.VisibilityCache.UpdateCacheReciprocal(allLivingCombatants);

                    // TODO: Set current stealth pips?
                }
            }
        }
Example #30
0
        private void CalculateDamages(Mech attacker, AbstractActor target)
        {
            Mod.MeleeLog.Info?.Write($"Calculating DFA damage for attacker: {CombatantUtils.Label(attacker)} " +
                                     $"vs. target: {CombatantUtils.Label(target)}");

            float targetTonnage = 0;

            if (target is Vehicle vehicle)
            {
                targetTonnage = vehicle.tonnage;
            }
            else if (target is Mech mech)
            {
                targetTonnage = mech.tonnage;
            }
            else if (target is Turret turret)
            {
                targetTonnage = turret.MeleeTonnage();
            }
            Mod.MeleeLog.Info?.Write($" - Tonnage => Attacker: {attacker.tonnage}  Target: {targetTonnage}");

            // split attacker damage into clusters
            float attackerDamage = attacker.DFAAttackerDamage(targetTonnage);

            attackerDamage = attacker.ApplyDFADamageReduction(attackerDamage);
            DamageHelper.ClusterDamage(attackerDamage, Mod.Config.Melee.DFA.DamageClusterDivisor, out this.AttackerDamageClusters);

            // split target damage into clusters
            float targetDamage = attacker.DFATargetDamage();

            targetDamage = target.ApplyDFADamageReduction(targetDamage);
            DamageHelper.ClusterDamage(targetDamage, Mod.Config.Melee.DFA.DamageClusterDivisor, out this.TargetDamageClusters);
        }