Пример #1
0
        public void TestPunchDamage_NoStats_UndamagedAttacker()
        {
            Mech attacker20 = TestHelper.BuildTestMech(tonnage: 20);
            ActorMeleeCondition attackerCondition20 = TestHelper.AllEnabledCondition(attacker20);

            ModState.meleeConditionCache[attacker20.DistinctId()] = attackerCondition20;
            Assert.AreEqual(10, attacker20.PunchDamage());

            Mech attacker50 = TestHelper.BuildTestMech(tonnage: 50);
            ActorMeleeCondition attackerCondition50 = TestHelper.AllEnabledCondition(attacker50);

            ModState.meleeConditionCache[attacker50.DistinctId()] = attackerCondition50;
            Assert.AreEqual(25, attacker50.PunchDamage());

            Mech attacker75 = TestHelper.BuildTestMech(tonnage: 75);
            ActorMeleeCondition attackerCondition75 = TestHelper.AllEnabledCondition(attacker75);

            ModState.meleeConditionCache[attacker75.DistinctId()] = attackerCondition75;
            Assert.AreEqual(38, attacker75.PunchDamage());

            Mech attacker100 = TestHelper.BuildTestMech(tonnage: 100);
            ActorMeleeCondition attackerCondition100 = TestHelper.AllEnabledCondition(attacker100);

            ModState.meleeConditionCache[attacker100.DistinctId()] = attackerCondition100;
            Assert.AreEqual(50, attacker100.PunchDamage());

            Mech attacker130 = TestHelper.BuildTestMech(tonnage: 130);
            ActorMeleeCondition attackerCondition130 = TestHelper.AllEnabledCondition(attacker130);

            ModState.meleeConditionCache[attacker130.DistinctId()] = attackerCondition130;
            Assert.AreEqual(65, attacker130.PunchDamage());
        }
        private bool ValidateAttack(Mech attacker, AbstractActor target, HashSet <MeleeAttackType> validAnimations)
        {
            ActorMeleeCondition meleeCondition = ModState.GetMeleeCondition(attacker);

            if (!meleeCondition.CanUsePhysicalAttack())
            {
                Mod.MeleeLog.Info?.Write($"Attacker cannot make a physical attack, skipping.");
                return(false);
            }

            // If no punch - we're not a valid attack.
            if (!validAnimations.Contains(MeleeAttackType.Punch))
            {
                Mod.MeleeLog.Info?.Write("Animations do not include a punch, cannot use physical weapon.");
                return(false);
            }

            if (target.UnaffectedPathing())
            {
                Mod.MeleeLog.Info?.Write($"Target is unaffected by pathing, likely a VTOL or LAM in flight. Cannot melee it!");
                return(false);
            }

            // If distance > walkSpeed, disable kick/physical weapon/punch
            if (!state.HasWalkAttackNodes)
            {
                Mod.MeleeLog.Info?.Write($"No walking nodes found for melee attack!");
                return(false);
            }

            Mod.MeleeLog.Info?.Write("PHYSICAL WEAPON ATTACK validated");
            return(true);
        }
Пример #3
0
        private bool ValidateAttack(Mech attacker, AbstractActor target)
        {
            ActorMeleeCondition meleeCondition = ModState.GetMeleeCondition(attacker);

            if (!meleeCondition.CanDFA())
            {
                Mod.MeleeLog.Info?.Write($"Attacker cannot DFA, skipping.");
                return(false);
            }

            if (!attacker.CanDFATargetFromPosition(target, attacker.CurrentPosition))
            {
                Mod.MeleeLog.Info?.Write($"Attacker unable to DFA target from their position.");
                return(false);
            }

            if (target.UnaffectedPathing())
            {
                Mod.MeleeLog.Info?.Write($"Target is unaffected by pathing, likely a VTOL or LAM in flight. Cannot melee it!");
                return(false);
            }

            // No damage check - by rules, you can DFA?
            Mod.MeleeLog.Info?.Write("DFA ATTACK validated");
            return(true);
        }
        public static float PhysicalWeaponInstability(this Mech mech)
        {
            ActorMeleeCondition attackerCondition = ModState.GetMeleeCondition(mech);

            if (!attackerCondition.CanUsePhysicalAttack())
            {
                return(0);
            }

            // 0 is a signal that there's no divisor
            float tonnageMulti = mech.StatCollection.ContainsStatistic(ModStats.PhysicalWeaponTargetInstability) &&
                                 mech.StatCollection.GetValue <float>(ModStats.PhysicalWeaponTargetInstability) > 0 ?
                                 mech.StatCollection.GetValue <float>(ModStats.PhysicalWeaponTargetInstability) :
                                 Mod.Config.Melee.PhysicalWeapon.DefaultInstabilityPerAttackerTon;

            float raw = (float)Math.Ceiling(tonnageMulti * mech.tonnage);

            Mod.MeleeLog.Debug?.Write($"PHYSICAL WEAPON instability => tonnageMulti: {tonnageMulti} x attacker tonnage: {mech.tonnage} = raw: {raw}");

            // Modifiers
            float mod = mech.StatCollection.ContainsStatistic(ModStats.PhysicalWeaponTargetInstabilityMod) ?
                        mech.StatCollection.GetValue <int>(ModStats.PhysicalWeaponTargetInstabilityMod) : 0f;
            float multi = mech.StatCollection.ContainsStatistic(ModStats.PhysicalWeaponTargetInstabilityMulti) ?
                          mech.StatCollection.GetValue <float>(ModStats.PhysicalWeaponTargetInstabilityMulti) : 1f;

            // Roll up final damage
            float final = (float)Math.Ceiling((raw + mod) * multi);

            Mod.MeleeLog.Debug?.Write($" - Target instability => final: {final} = (raw: {raw} + mod: {mod}) x multi: {multi}");

            return(final);
        }
Пример #5
0
        private bool ValidateAttack(Mech attacker, AbstractActor target, HashSet <MeleeAttackType> validAnimations)
        {
            ActorMeleeCondition meleeCondition = ModState.GetMeleeCondition(attacker);

            if (!meleeCondition.CanKick())
            {
                Mod.MeleeLog.Info?.Write($"Attacker cannot kick, skipping.");
                return(false);
            }

            // If neither kick (mech) or stomp (vehicle) - we're not a valid attack.
            if (!validAnimations.Contains(MeleeAttackType.Kick) && !validAnimations.Contains(MeleeAttackType.Stomp))
            {
                Mod.MeleeLog.Info?.Write("Animations do not include a kick or stomp, cannot kick.");
                return(false);
            }

            if (target.UnaffectedPathing())
            {
                Mod.MeleeLog.Info?.Write($"Target is unaffected by pathing, likely a VTOL or LAM in flight. Cannot melee it!");
                return(false);
            }

            // If distance > walkSpeed, disable kick/physical weapon/punch
            if (!state.HasWalkAttackNodes)
            {
                Mod.MeleeLog.Info?.Write($"No walking nodes found for melee attack!");
                return(false);
            }

            Mod.MeleeLog.Info?.Write("KICK ATTACK validated");
            return(true);
        }
        public static ActorMeleeCondition AllEnabledCondition(Mech mech)
        {
            ActorMeleeCondition mmc = new ActorMeleeCondition(mech,
                                                              true, true, 2, 2, true, true,
                                                              true, true, 2, 2, true, true,
                                                              true, true);

            return(mmc);
        }
        public static float PunchInstability(this Mech mech)
        {
            ActorMeleeCondition attackerCondition = ModState.GetMeleeCondition(mech);

            if (!attackerCondition.CanPunch())
            {
                return(0);
            }

            // 0 is a signal that there's no divisor
            float tonnageMulti = mech.StatCollection.ContainsStatistic(ModStats.PunchTargetInstability) &&
                                 mech.StatCollection.GetValue <float>(ModStats.PunchTargetInstability) > 0 ?
                                 mech.StatCollection.GetValue <float>(ModStats.PunchTargetInstability) :
                                 Mod.Config.Melee.Punch.TargetInstabilityPerAttackerTon;

            float raw = (float)Math.Ceiling(tonnageMulti * mech.tonnage);

            Mod.MeleeLog.Debug?.Write($"PUNCH instability => tonnageMulti: {tonnageMulti} x attacker tonnage: {mech.tonnage} = raw: {raw}");

            // Modifiers
            float mod = mech.StatCollection.ContainsStatistic(ModStats.PunchTargetInstabilityMod) ?
                        mech.StatCollection.GetValue <int>(ModStats.PunchTargetInstabilityMod) : 0f;
            float multi = mech.StatCollection.ContainsStatistic(ModStats.PunchTargetInstabilityMulti) ?
                          mech.StatCollection.GetValue <float>(ModStats.PunchTargetInstabilityMulti) : 1f;

            // Leg actuator damage
            float leftReductionMulti = 1f;
            int   damagedLeftCount   = 2 - attackerCondition.LeftArmActuatorsCount;

            for (int i = 0; i < damagedLeftCount; i++)
            {
                leftReductionMulti *= Mod.Config.Melee.Punch.ArmActuatorDamageReduction;
            }
            Mod.MeleeLog.Debug?.Write($" - Left actuator damage multi is: {leftReductionMulti}");

            float rightReductionMulti = 1f;
            int   damagedRightCount   = 2 - attackerCondition.RightArmActuatorsCount;

            for (int i = 0; i < damagedRightCount; i++)
            {
                rightReductionMulti *= Mod.Config.Melee.Punch.ArmActuatorDamageReduction;
            }
            Mod.MeleeLog.Debug?.Write($" - Right actuator damage multi is: {rightReductionMulti}");

            float actuatorMulti = leftReductionMulti >= rightReductionMulti ? leftReductionMulti : rightReductionMulti;

            Mod.MeleeLog.Debug?.Write($" - Using actuator damage multi of: {actuatorMulti}");

            // Roll up instability
            float final = (float)Math.Ceiling((raw + mod) * multi * actuatorMulti);

            Mod.MeleeLog.Info?.Write($" - Target instability => final: {final} = (raw: {raw} + mod: {mod}) x " +
                                     $"multi: {multi} x actuatorMulti: {actuatorMulti}");

            return(final);
        }
        public static float PunchDamage(this Mech mech)
        {
            ActorMeleeCondition attackerCondition = ModState.GetMeleeCondition(mech);

            if (!attackerCondition.CanPunch())
            {
                return(0);
            }

            float tonnageMulti = mech.StatCollection.ContainsStatistic(ModStats.PunchTargetDamage) &&
                                 mech.StatCollection.GetValue <float>(ModStats.PunchTargetDamage) > 0 ?
                                 mech.StatCollection.GetValue <float>(ModStats.PunchTargetDamage) :
                                 Mod.Config.Melee.Punch.TargetDamagePerAttackerTon;

            float raw = (float)Math.Ceiling(tonnageMulti * mech.tonnage);

            Mod.MeleeLog.Debug?.Write($"PUNCH damage => tonnageMulti: {tonnageMulti} x attacker tonnage: {mech.tonnage} = raw: {raw}");

            // Modifiers
            float mod = mech.StatCollection.ContainsStatistic(ModStats.PunchTargetDamageMod) ?
                        mech.StatCollection.GetValue <int>(ModStats.PunchTargetDamageMod) : 0f;
            float multi = mech.StatCollection.ContainsStatistic(ModStats.PunchTargetDamageMulti) ?
                          mech.StatCollection.GetValue <float>(ModStats.PunchTargetDamageMulti) : 1f;

            // Actuator damage
            float leftReductionMulti   = 1f;
            int   damagedLeftActuators = 2 - attackerCondition.LeftArmActuatorsCount;

            for (int i = 0; i < damagedLeftActuators; i++)
            {
                leftReductionMulti *= Mod.Config.Melee.Punch.ArmActuatorDamageReduction;
            }
            Mod.MeleeLog.Debug?.Write($" - Left arm actuator damage is: {leftReductionMulti}");

            float rightReductionMulti   = 1f;
            int   damagedRightActuators = 2 - attackerCondition.RightArmActuatorsCount;

            for (int i = 0; i < damagedRightActuators; i++)
            {
                rightReductionMulti *= Mod.Config.Melee.Punch.ArmActuatorDamageReduction;
            }
            Mod.MeleeLog.Debug?.Write($" - Right arm actuator damage is: {rightReductionMulti}");

            float reductionMulti = leftReductionMulti >= rightReductionMulti ? leftReductionMulti : rightReductionMulti;

            Mod.MeleeLog.Debug?.Write($" - Using arm actuator damage reduction of: {reductionMulti}");

            // Roll up final damage
            float final = (float)Math.Ceiling((raw + mod) * multi * reductionMulti);

            Mod.MeleeLog.Debug?.Write($" - Target damage per strike => final: {final} = (raw: {raw} + mod: {mod}) x " +
                                      $"multi: {multi} x reductionMulti: {reductionMulti}");

            return(final);
        }
Пример #9
0
        public void TestPunchDamage_TargetDamageStatMods_UndamagedAttacker()
        {
            // Test override stat
            Mech attacker50 = TestHelper.BuildTestMech(tonnage: 50);
            ActorMeleeCondition attackerCondition50 = TestHelper.AllEnabledCondition(attacker50);

            ModState.meleeConditionCache[attacker50.DistinctId()] = attackerCondition50;

            attacker50.StatCollection.AddStatistic <int>(ModStats.PunchTargetDamageMod, 50);
            attacker50.StatCollection.AddStatistic <float>(ModStats.PunchTargetDamageMulti, 10f);
            // RoundUP ((( 0.5 * 50) + 50) * 10 => 750
            Assert.AreEqual(750, attacker50.PunchDamage());
        }
        public static float KickInstability(this Mech mech)
        {
            ActorMeleeCondition attackerCondition = ModState.GetMeleeCondition(mech);

            if (!attackerCondition.CanKick())
            {
                return(0);
            }

            float raw = (float)Math.Ceiling(Mod.Config.Melee.Kick.TargetInstabilityPerAttackerTon * mech.tonnage);

            Mod.MeleeLog.Debug?.Write($"KICK baseStability: {Mod.Config.Melee.Punch.TargetInstabilityPerAttackerTon} x " +
                                      $"attacker tonnage: {mech.tonnage} = {raw}");

            // Modifiers
            float mod = mech.StatCollection.ContainsStatistic(ModStats.KickTargetInstabilityMod) ?
                        mech.StatCollection.GetValue <int>(ModStats.KickTargetInstabilityMod) : 0f;
            float multi = mech.StatCollection.ContainsStatistic(ModStats.KickTargetInstabilityMulti) ?
                          mech.StatCollection.GetValue <float>(ModStats.KickTargetInstabilityMulti) : 1f;

            // Leg actuator damage
            float leftReductionMulti = 1f;
            int   damagedLeftCount   = 2 - attackerCondition.LeftLegActuatorsCount;

            for (int i = 0; i < damagedLeftCount; i++)
            {
                leftReductionMulti *= Mod.Config.Melee.Kick.LegActuatorDamageReduction;
            }
            Mod.MeleeLog.Debug?.Write($" - Left actuator damage multi is: {leftReductionMulti}");

            float rightReductionMulti = 1f;
            int   damagedRight        = 2 - attackerCondition.RightLegActuatorsCount;

            for (int i = 0; i < damagedRight; i++)
            {
                rightReductionMulti *= Mod.Config.Melee.Kick.LegActuatorDamageReduction;
            }
            Mod.MeleeLog.Debug?.Write($" - Right actuator damage multi is: {rightReductionMulti}");

            float actuatorMulti = leftReductionMulti >= rightReductionMulti ? leftReductionMulti : rightReductionMulti;

            Mod.MeleeLog.Debug?.Write($" - Using actuator damage multi of: {actuatorMulti}");

            // Roll up instability
            float final = (float)Math.Ceiling((raw + mod) * multi * actuatorMulti);

            Mod.MeleeLog.Debug?.Write($" - Target instability => final: {final} = (raw: {raw} + mod: {mod}) x " +
                                      $"multi: {multi} x actuatorMulti: {actuatorMulti}");

            return(final);
        }
Пример #11
0
        private void CalculateModifiers(Mech attacker, AbstractActor target)
        {
            // -2 to hit base
            this.AttackModifiers.Add(ModText.LT_Label_Easy_to_Kick, Mod.Config.Melee.Kick.BaseAttackBonus);

            // If target is prone, -2 modifier
            if (target.IsProne)
            {
                this.AttackModifiers.Add(ModText.LT_Label_Target_Prone, Mod.Config.Melee.ProneTargetAttackModifier);
            }

            // Actuator damage; +1 for foot actuator, +2 to hit for each upper/lower actuator hit
            ActorMeleeCondition attackerCondition = ModState.GetMeleeCondition(attacker);
            int leftLegMalus = (2 - attackerCondition.LeftLegActuatorsCount) * Mod.Config.Melee.Kick.LegActuatorDamageMalus;

            if (!attackerCondition.LeftFootIsFunctional)
            {
                leftLegMalus += Mod.Config.Melee.Kick.FootActuatorDamageMalus;
            }

            int rightLegMalus = (2 - attackerCondition.RightLegActuatorsCount) * Mod.Config.Melee.Kick.LegActuatorDamageMalus;

            if (!attackerCondition.RightFootIsFunctional)
            {
                rightLegMalus += Mod.Config.Melee.Kick.FootActuatorDamageMalus;
            }

            int bestLegMalus = leftLegMalus <= rightLegMalus ? leftLegMalus : rightLegMalus;

            if (bestLegMalus != 0)
            {
                this.AttackModifiers.Add(ModText.LT_Label_Actuator_Damage, bestLegMalus);
            }

            // Check for attack modifier statistic
            if (attacker.StatCollection.ContainsStatistic(ModStats.KickAttackMod) &&
                attacker.StatCollection.GetValue <int>(ModStats.KickAttackMod) != 0)
            {
                this.AttackModifiers.Add(ModText.LT_Label_Kick_Attack_Mod, attacker.StatCollection.GetValue <int>(ModStats.KickAttackMod));
            }
        }
Пример #12
0
        private bool ValidateAttack(Mech attacker, AbstractActor target, HashSet <MeleeAttackType> validAnimations)
        {
            ActorMeleeCondition meleeCondition = ModState.GetMeleeCondition(attacker);

            if (!meleeCondition.CanCharge())
            {
                Mod.MeleeLog.Info?.Write($"Attacker cannot charge, skipping.");
                return(false);
            }

            // If neither tackle (mech) or stomp (vehicle) - we're not a valid attack.
            if (!validAnimations.Contains(MeleeAttackType.Tackle) && !validAnimations.Contains(MeleeAttackType.Stomp))
            {
                Mod.MeleeLog.Info?.Write("Animations do not include a tackle or stomp, attacker cannot charge!");
                return(false);
            }

            // Charges cannot target prone units
            if (target.IsProne)
            {
                Mod.MeleeLog.Info?.Write($"Attacker unable to charge prone target");
                return(false);
            }

            if (target.UnaffectedPathing())
            {
                Mod.MeleeLog.Info?.Write($"Target is unaffected by pathing, likely a VTOL or LAM in flight. Cannot melee it!");
                return(false);
            }

            // If distance > walkSpeed, disable kick/physical weapon/punch
            if (!state.HasSprintAttackNodes)
            {
                Mod.MeleeLog.Info?.Write($"No sprinting nodes found for melee attack!");
                return(false);
            }

            Mod.MeleeLog.Info?.Write("CHARGE ATTACK validated");
            return(true);
        }
        private void CalculateModifiers(Mech attacker, AbstractActor target)
        {
            // If target is prone, -2 modifier
            if (target.IsProne)
            {
                this.AttackModifiers.Add(ModText.LT_Label_Target_Prone, Mod.Config.Melee.ProneTargetAttackModifier);
            }

            // +2 to hit for each upper/lower actuator hit
            ActorMeleeCondition attackerCondition = ModState.GetMeleeCondition(attacker);
            int leftArmMalus  = (2 - attackerCondition.LeftArmActuatorsCount) * Mod.Config.Melee.Punch.ArmActuatorDamageMalus;
            int rightArmMalus = (2 - attackerCondition.RightArmActuatorsCount) * Mod.Config.Melee.Punch.ArmActuatorDamageMalus;
            int bestMalus     = leftArmMalus <= rightArmMalus ? leftArmMalus : rightArmMalus;

            // If the ignore actuators stat is set, set the malus to 0 regardless of damage
            Statistic ignoreActuatorsStat = attacker.StatCollection.GetStatistic(ModStats.PhysicalWeaponIgnoreActuators);

            if (ignoreActuatorsStat != null && ignoreActuatorsStat.Value <bool>())
            {
                bestMalus = 0;
            }

            // Add actuator damage if it exists
            if (bestMalus != 0)
            {
                this.AttackModifiers.Add(ModText.LT_Label_Actuator_Damage, bestMalus);
            }

            // Check for attack modifier statistic
            Statistic phyWeapAttackMod = attacker.StatCollection.GetStatistic(ModStats.PhysicalWeaponAttackMod);

            if (phyWeapAttackMod != null && phyWeapAttackMod.Value <int>() != 0)
            {
                this.AttackModifiers.Add(ModText.LT_Label_Physical_Weapon_Attack_Mod, attacker.StatCollection.GetValue <int>(ModStats.PhysicalWeaponAttackMod));
            }
        }
Пример #14
0
        public void TestPunchDamage_TargetDamageStat_UndamagedAttacker()
        {
            // Test override stat
            Mech attacker50 = TestHelper.BuildTestMech(tonnage: 50);
            ActorMeleeCondition attackerCondition50 = TestHelper.AllEnabledCondition(attacker50);

            ModState.meleeConditionCache[attacker50.DistinctId()] = attackerCondition50;

            attacker50.StatCollection.AddStatistic <float>(ModStats.PunchTargetDamage, 2f);
            Assert.AreEqual(100, attacker50.PunchDamage());

            // Test override stat @ 0; should default to ModValue
            Mod.Config.Melee.Punch.TargetDamagePerAttackerTon = 3.0f;
            attacker50.StatCollection.Set <float>(ModStats.PunchTargetDamage, 0);
            Assert.AreEqual(150, attacker50.PunchDamage());

            // Test override stat @ negatives; should default to modValue
            Mod.Config.Melee.Punch.TargetDamagePerAttackerTon = 4.0f;
            attacker50.StatCollection.Set <float>(ModStats.PunchTargetDamage, -20);
            Assert.AreEqual(200, attacker50.PunchDamage());

            // Reset for other tests
            Mod.Config.Melee.Punch.TargetDamagePerAttackerTon = 0.5f;
        }
        public static bool CanMakePhysicalWeaponAttack(this Mech mech)
        {
            ActorMeleeCondition attackerCondition = ModState.GetMeleeCondition(mech);

            return(attackerCondition.CanUsePhysicalAttack());
        }