示例#1
0
        private static float GetAccuracy(Thing weapon, VerbProperties verb, ProjectileProperties projectile, float dist, Pawn pawn = null)
        {
            float forcedMissRadius = CalculateAdjustedForcedMissDist(verb.ForcedMissRadius, dist);
            float baseAimOn        = verb.GetHitChanceFactor(weapon, dist);

            if (pawn != null)
            {
                baseAimOn *= ShotReport.HitFactorFromShooter(pawn, dist);
            }
            int affectedCellCount = (verb.CausesExplosion) ? GenRadial.NumCellsInRadius(projectile.explosionRadius) : 1;

            float accuracy;

            if (forcedMissRadius > 0.5f)
            {
                int affectableCellCount = GenRadial.NumCellsInRadius(forcedMissRadius);
                accuracy = (float)affectedCellCount / affectableCellCount;
            }
            else
            {
                float medianToWildRadius = ShootTuning.MissDistanceFromAimOnChanceCurves.Evaluate(baseAimOn, 0.5f);
                float indirectHitChance  = (float)(affectedCellCount - 1) / GenRadial.NumCellsInRadius(medianToWildRadius);
                accuracy = baseAimOn + (1f - baseAimOn) * indirectHitChance;
            }
            return(Mathf.Clamp01(accuracy));
        }
        /// <summary>
        /// Gets the adjusted hit chance factor of a shot.  This is equivalent to shootVerb.GetHitChanceFactor() unless
        /// a shooter is provided, in which case it will also be adjusted based on the shooter's hit chance.
        ///
        /// This value can be greater than 1.0 in the case of weapons with overcapped accuracy.
        /// </summary>
        /// <returns>The adjusted hit chance factor.</returns>
        /// <param name="range">The range of the shot.</param>
        /// <param name="shooter">(Optional) The turret or pawn shooting the weapon.</param>
        public float GetAdjustedHitChanceFactor(float range, Thing shooter = null)
        {
            float hitChance = shootVerb.GetHitChanceFactor(weapon, range);

            if (shooter != null)
            {
                hitChance *= ShotReport.HitFactorFromShooter(shooter, range);
            }

            return(hitChance);
        }
示例#3
0
        public override string GetExplanationFinalizePart(StatRequest req, ToStringNumberSense numberSense, float finalVal)
        {
            StringBuilder stringBuilder = new StringBuilder();

            for (int i = 5; i <= 45; i += 5)
            {
                float f = ShotReport.HitFactorFromShooter(finalVal, i);
                stringBuilder.AppendLine("distance".Translate().CapitalizeFirst() + " " + i.ToString() + ": " + f.ToStringPercent("F1"));
            }
            stringBuilder.AppendLine(base.GetExplanationFinalizePart(req, numberSense, finalVal));
            return(stringBuilder.ToString());
        }
示例#4
0
        public float GetAimOnTargetChance()
        {
            var distance = (Target - Origin).LengthHorizontal;

            var factorFromShooterAndDist = ShotReport.HitFactorFromShooter(Caster, distance);

            var factorFromEquipment = _weaponVerb.verbProps.GetHitChanceFactor(
                _weaponVerb.EquipmentSource, distance);

            var factorFromWeather = 1f;

            if (!Caster.Position.Roofed(CasterMap) || !Target.Roofed(CasterMap))
            {
                factorFromWeather = CasterMap.weatherManager.CurWeatherAccuracyMultiplier;
            }

            ThingDef coveringGas = null;

            foreach (var point in GenSight.PointsOnLineOfSight(Origin, Target))
            {
                if (!point.CanBeSeenOver(CasterMap))
                {
                    break;
                }

                Thing gas = point.GetGas(CasterMap);
                if (IsThisGasMorePenalisingThan(gas, coveringGas))
                {
                    coveringGas = gas.def;
                }
            }

            var factorFromCoveringGas = 1f - (coveringGas?.gas.accuracyPenalty ?? 0f);

            var result = factorFromShooterAndDist * factorFromEquipment * factorFromWeather *
                         factorFromCoveringGas;

            if (result < 0.0201f)
            {
                result = 0.0201f;
            }

            return(result);
        }
示例#5
0
        public override float GetValueUnfinalized(StatRequest req, bool applyPostProcess = true)
        {
            if (!(req.Def is ThingDef def))
            {
                return(0.0f);
            }

            CompEquippable         equipComp = null;
            CompLootAffixableThing lootComp  = null;

            if (req.HasThing)
            {
                var thing = (ThingWithComps)req.Thing;
                equipComp = thing.TryGetComp <CompEquippable>();
                lootComp  = thing.TryGetComp <CompLootAffixableThing>();
            }

            Verb           verb      = equipComp?.AllVerbs.First(v => v.verbProps.isPrimary);
            VerbProperties verbProps = verb != null ? verb.verbProps : def.Verbs.First(vp => vp.isPrimary);
            Pawn           attacker  = req.HasThing ? GetCurrentWeaponUser(req.Thing) : null;
            var            projProps = verbProps.defaultProjectile.projectile;

            var projModifier = (LootAffixModifier_VerbPropertiesChange_Def)lootComp?.AllModifiers.FirstOrFallback(
                lam => lam.AppliesTo == ModifierTarget.VerbProperties && lam is LootAffixModifier_VerbPropertiesChange_Def lamVPCD && lamVPCD.affectedField == "defaultProjectile"
                );
            ThingDef modProjectile = projModifier != null ? (ThingDef)projModifier.resolvedDef : null;
            var      modProjProps  = modProjectile?.projectile;

            float chance = modProjProps != null ? 1f - projModifier.GetRealChance(lootComp.parent) : 1f;

            if (chance <= 0.05f)
            {
                chance = 1f;                   // already permanently set to "base" verbProps
            }
            float baseDamage =
                projProps.damageDef.harmsHealth == false ? 0f :
                req.HasThing         ? projProps.GetDamageAmount(req.Thing) :
                req.StuffDef != null?projProps.GetDamageAmount(def.GetStatValueAbstract(StatDefOf.RangedWeapon_DamageMultiplier, req.StuffDef)) :
                    projProps.GetDamageAmount(def.GetStatValueAbstract(StatDefOf.RangedWeapon_DamageMultiplier))
            ;

            float damage = baseDamage * verbProps.burstShotCount * chance;

            if (chance < 1f)
            {
                float modChance     = 1f - chance;
                float modBaseDamage =
                    modProjProps.damageDef.harmsHealth == false ? 0f :
                    modProjProps.GetDamageAmount(req.Thing)
                ;

                damage += modBaseDamage * verbProps.burstShotCount * modChance;
            }

            // FIXME: Confirm warmupTime (and AimingDelayFactor) is used in a full shot cycle
            // FIXME: warmupTime * this.CasterPawn.GetStatValue(StatDefOf.AimingDelayFactor, true)).SecondsToTicks()
            float secondsSpent = 0;

            if (verb != null)
            {
                secondsSpent = verbProps.AdjustedFullCycleTime(verb, attacker);
            }
            else
            {
                secondsSpent  = verbProps.warmupTime + ((verbProps.burstShotCount - 1) * verbProps.ticksBetweenBurstShots).TicksToSeconds();
                secondsSpent +=
                    req.HasThing         ? req.Thing.GetStatValue(StatDefOf.RangedWeapon_Cooldown, true) :
                    req.StuffDef != null?def.GetStatValueAbstract(StatDefOf.RangedWeapon_Cooldown, req.StuffDef) :
                        def.GetStatValueAbstract(StatDefOf.RangedWeapon_Cooldown)
                ;
            }

            // Every integer range possible as an average
            float avgAccuracy = 0;

            for (int i = 3; i <= verbProps.range; i++)
            {
                float rngAccuracy = verbProps.GetHitChanceFactor(req.Thing, i);
                if (attacker != null)
                {
                    rngAccuracy *= ShotReport.HitFactorFromShooter(attacker, i);
                }
                avgAccuracy += rngAccuracy;
            }
            if (verbProps.range >= 3)
            {
                avgAccuracy /= verbProps.range - 2;
            }

            return(secondsSpent == 0 ? 0.0f : damage / secondsSpent * avgAccuracy);
        }
示例#6
0
        public override string GetExplanationUnfinalized(StatRequest req, ToStringNumberSense numberSense)
        {
            if (!(req.Def is ThingDef def))
            {
                return(null);
            }

            /* Damage section */
            CompEquippable         equipComp = null;
            CompLootAffixableThing lootComp  = null;

            if (req.HasThing)
            {
                var thing = (ThingWithComps)req.Thing;
                equipComp = thing.TryGetComp <CompEquippable>();
                lootComp  = thing.TryGetComp <CompLootAffixableThing>();
            }

            Verb           verb      = equipComp?.AllVerbs.First(v => v.verbProps.isPrimary);
            VerbProperties verbProps = verb != null ? verb.verbProps : def.Verbs.First(vp => vp.isPrimary);
            Pawn           attacker  = req.HasThing ? GetCurrentWeaponUser(req.Thing) : null;
            var            projProps = verbProps.defaultProjectile.projectile;

            var projModifier = (LootAffixModifier_VerbPropertiesChange_Def)lootComp?.AllModifiers.FirstOrFallback(
                lam => lam.AppliesTo == ModifierTarget.VerbProperties && lam is LootAffixModifier_VerbPropertiesChange_Def lamVPCD && lamVPCD.affectedField == "defaultProjectile"
                );
            ThingDef modProjectile = projModifier != null ? (ThingDef)projModifier.resolvedDef : null;
            var      modProjProps  = modProjectile?.projectile;

            float chance = modProjProps != null ? 1f - projModifier.GetRealChance(lootComp.parent) : 1f;

            if (chance <= 0.05f)
            {
                chance = 1f;                   // already permanently set to "base" verbProps
            }
            string chanceStr = GenText.ToStringPercent(chance);

            float baseDamage =
                projProps.damageDef.harmsHealth == false ? 0f :
                req.HasThing         ? projProps.GetDamageAmount(req.Thing) :
                req.StuffDef != null?projProps.GetDamageAmount(def.GetStatValueAbstract(StatDefOf.RangedWeapon_DamageMultiplier, req.StuffDef)) :
                    projProps.GetDamageAmount(def.GetStatValueAbstract(StatDefOf.RangedWeapon_DamageMultiplier))
            ;

            float damage = baseDamage * verbProps.burstShotCount * chance;

            string reportText = "Damage".Translate() + ":\n";

            if (chance < 1f)
            {
                reportText += "    " + "RimLoot_StatsReport_ProjectileWithChance".Translate(
                    verbProps.defaultProjectile.Named("PROJECTILE"), chanceStr.Named("chance")
                    ) + "\n";
                reportText += string.Format("    {0}: {1} * {2} * {3} = {4}\n\n",
                                            "Damage".Translate(),
                                            baseDamage.ToStringDecimalIfSmall(),
                                            verbProps.burstShotCount,
                                            chanceStr,
                                            damage.ToStringDecimalIfSmall()
                                            );

                float  modChance    = 1f - chance;
                string modChanceStr = GenText.ToStringPercent(modChance);

                float modBaseDamage =
                    modProjProps.damageDef.harmsHealth == false ? 0f :
                    modProjProps.GetDamageAmount(req.Thing)
                ;
                float modDamage = modBaseDamage * verbProps.burstShotCount * modChance;

                reportText += "    " + "RimLoot_StatsReport_ProjectileWithChance".Translate(
                    modProjectile.Named("PROJECTILE"), modChanceStr.Named("chance")
                    ) + "\n";
                reportText += string.Format("    {0}: {1} * {2} * {3} = {4}\n\n",
                                            "Damage".Translate(),
                                            modBaseDamage.ToStringDecimalIfSmall(),
                                            verbProps.burstShotCount,
                                            modChanceStr,
                                            modDamage.ToStringDecimalIfSmall()
                                            );

                reportText += string.Format("{0}: {1}\n\n", "StatsReport_TotalValue".Translate(), (damage + modDamage).ToStringDecimalIfSmall());
            }
            else
            {
                reportText += "    " + "RimLoot_StatsReport_Projectile".Translate(verbProps.defaultProjectile.Named("PROJECTILE")) + "\n";
                reportText += string.Format("    {0}: {1} * {2} = {3}\n\n",
                                            "Damage".Translate(),
                                            baseDamage.ToStringDecimalIfSmall(),
                                            verbProps.burstShotCount,
                                            damage.ToStringDecimalIfSmall()
                                            );
            }

            /* Seconds per attack */
            float secondsSpent = 0;
            float cooldown     =
                req.HasThing         ? req.Thing.GetStatValue(StatDefOf.RangedWeapon_Cooldown, true) :
                req.StuffDef != null?def.GetStatValueAbstract(StatDefOf.RangedWeapon_Cooldown, req.StuffDef) :
                    def.GetStatValueAbstract(StatDefOf.RangedWeapon_Cooldown)
            ;

            float burstShotTime = ((verbProps.burstShotCount - 1) * verbProps.ticksBetweenBurstShots).TicksToSeconds();

            if (verb != null)
            {
                secondsSpent = verbProps.AdjustedFullCycleTime(verb, attacker);
            }
            else
            {
                secondsSpent = verbProps.warmupTime + cooldown + burstShotTime;
            }

            reportText += GenText.ToTitleCaseSmart("SecondsPerAttackLower".Translate()) + ":\n";
            reportText += string.Format("    {0}: {1}\n", "WarmupTime".Translate(), "PeriodSeconds".Translate(verbProps.warmupTime.ToStringDecimalIfSmall()));
            if (burstShotTime > 0)
            {
                reportText += string.Format("    {0}: {1}\n", "BurstShotFireRate".Translate(), "PeriodSeconds".Translate(burstShotTime.ToStringDecimalIfSmall()));
            }
            reportText += string.Format("    {0}: {1}\n", "CooldownTime".Translate(), "PeriodSeconds".Translate(cooldown.ToStringDecimalIfSmall()));
            reportText += string.Format("{0}: {1}\n\n", "StatsReport_TotalValue".Translate(), "PeriodSeconds".Translate(secondsSpent.ToStringDecimalIfSmall()));

            /* Average accuracy */

            // Every integer range possible as an average
            float wpnAccuracy  = 0;
            float pawnAccuracy = 0;

            for (int i = 3; i <= verbProps.range; i++)
            {
                wpnAccuracy += verbProps.GetHitChanceFactor(req.Thing, i);
                if (attacker != null)
                {
                    pawnAccuracy += ShotReport.HitFactorFromShooter(attacker, i);
                }
            }
            if (verbProps.range >= 3)
            {
                wpnAccuracy /= verbProps.range - 2;
                if (attacker != null)
                {
                    pawnAccuracy /= verbProps.range - 2;
                }
            }

            reportText += "AverageAccuracy".Translate() + ":\n";
            reportText += string.Format("    {0}: {1}\n", "ShootReportWeapon".Translate(), wpnAccuracy.ToStringPercent("F1"));
            if (pawnAccuracy > 0)
            {
                reportText += string.Format("    {0}: {1}\n", "ShootReportShooterAbility".Translate(), pawnAccuracy.ToStringPercent("F1"));
            }

            return(reportText);
        }
        public static string CombatPart(Pawn pawn, Comp_NightVision comp)
        {
            var         strHelper       = new CombatStatStrHelper();
            const float NoLight         = 0f;
            const float FullLight       = 1f;
            float       noLightFactor   = comp.FactorFromGlow(glow: NoLight);
            float       fullLightFactor = comp.FactorFromGlow(glow: FullLight);


            strHelper.AddMainHeader();

            if (Settings.CombatStore.RangedHitEffectsEnabled.Value)
            {
                if (ShowRangedEffectsForPawn(pawn))
                {
                    strHelper.AddLine(Str_Combat.ShootTargetAtGlow());


                    for (var i = 1; i <= 4; i++)
                    {
                        float hit = ShotReport.HitFactorFromShooter(caster: pawn, distance: i * 5);

                        strHelper.AddLine(
                            Str_Combat.ShotChanceTransform(
                                distance: i * 5,
                                hitChance: hit,
                                nvResult: CombatHelpers.HitChanceGlowTransform(hitChance: hit,
                                                                               attGlowFactor: noLightFactor),
                                psResult: CombatHelpers.HitChanceGlowTransform(hitChance: hit,
                                                                               attGlowFactor: fullLightFactor)
                                )
                            );
                    }

                    strHelper.NextLine();
                }
                else
                {
                    // TODO add line reporting why effects are not appearing
                }
            }

            if (Settings.CombatStore.MeleeHitEffectsEnabled.Value)
            {
                if (ShowMeleeEffectsForPawn(pawn))
                {
                    float pawnDodgeVal = pawn.GetStatValue(stat: Defs_Rimworld.MeleeDodgeStat);
                    float meleeHit     = pawn.GetStatValue(stat: Defs_Rimworld.MeleeHitStat, applyPostProcess: true);

                    var caps = Settings.Store.MultiplierCaps;

                    strHelper.AddLine(Str_Combat.StrikeTargetAtGlow());


                    strHelper.AddLine(
                        Str_Combat.StrikeChanceTransform(
                            hitChance: meleeHit,
                            nvResult: CombatHelpers.HitChanceGlowTransform(hitChance: meleeHit,
                                                                           attGlowFactor: noLightFactor),
                            psResult: CombatHelpers.HitChanceGlowTransform(hitChance: meleeHit,
                                                                           attGlowFactor: fullLightFactor)
                            )
                        );

                    strHelper.NextLine();

                    strHelper.AddSurpriseAttackHeader();

                    ///////////////////////////////////////////
                    //// Surprise attack stats at 0% light ////

                    float noLightSurpAttChance =
                        CombatHelpers.SurpriseAttackChance(atkGlowFactor: noLightFactor, defGlowFactor: caps.min);
                    // attack vs pawn with minimum LM
                    strHelper.AddSurpriseAttackRow(NoLight, noLightFactor, caps.min);

                    // skip if we need more room to show dodge stats
                    if (pawnDodgeVal.IsTrivial())
                    {
                        //skip if chance was 0% vs pawn with min LM (as it won't be different
                        if (noLightSurpAttChance.IsNonTrivial())
                        {
                            // attack vs pawn with normal LM
                            noLightSurpAttChance =
                                CombatHelpers.SurpriseAttackChance(atkGlowFactor: noLightFactor, defGlowFactor: 1f);

                            strHelper.AddSurpriseAttackRow(NoLight, noLightFactor, 1);

                            // skip as above
                            if (noLightSurpAttChance.IsNonTrivial())
                            {
                                // attack vs pawn with max LM
                                strHelper.AddSurpriseAttackRow(0, noLightFactor, caps.max);
                            }
                        }
                    }
                    ////////////////////////////////////////////

                    /////////////////////////////////////////////
                    //// Surprise attack stats at 100% light ////

                    // skip if we need more room to show dodge stats
                    if (pawnDodgeVal.IsTrivial())
                    {
                        // attack vs pawn with min LM
                        strHelper.AddSurpriseAttackRow(fullLightFactor, fullLightFactor, caps.min);
                    }

                    // attack vs pawn with normal LM
                    strHelper.AddSurpriseAttackRow(fullLightFactor, fullLightFactor, 1f);
                    strHelper.NextLine();
                    /////////////////////////////////////////////

                    /////////////////////////////////////////////
                    ////             Dodge Stats             ////


                    strHelper.AddDodgeHeader();

                    // This pawns chance to dodge when attacked

                    // attacked by pawn with min LM in no light
                    strHelper.AddDodgeRow(NoLight, caps.min, noLightFactor, pawnDodgeVal);

                    // skip if pawns dodge value is zero
                    if (pawnDodgeVal.IsNonTrivial())
                    {
                        // attacked by pawn with normal LM in no light
                        strHelper.AddDodgeRow(NoLight, 1f, noLightFactor, pawnDodgeVal);

                        // attacked by pawn with max LM in no light
                        strHelper.AddDodgeRow(NoLight, caps.max, noLightFactor, pawnDodgeVal);

                        // attacked by pawn with min LM in no light
                        strHelper.AddDodgeRow(FullLight, caps.min, fullLightFactor, pawnDodgeVal);
                    }

                    //attacked by pawn with normal LM in full light
                    strHelper.AddDodgeRow(FullLight, 1, fullLightFactor, pawnDodgeVal);
                }
            }

            return(strHelper.ToString());
        }
示例#8
0
        private static float BaseArcheryEffectivenessFor(Pawn pawn)
        {
            float accuracy = ShotReport.HitFactorFromShooter(pawn, archeryDistance);

            return(accuracy * MentalBreakThresholdMultiplierFor(pawn, 0.25f) * ArcheryEffectivenessNormaliser);
        }