Exemple #1
0
        public float AdjustShotHeight(Thing caster, LocalTargetInfo target, ref float shotHeight)
        {
            /* TODO:  This really should determine how much the shooter needs to rise up for a *good* shot.
             * If we're shooting at something tall, we might not need to rise at all, if we're shooting at
             * something short, we might need to rise *more* than just above the cover.  This at least handles
             * cases where we're below cover, but the taret is taller than the cover */
            GetHighestCoverAndSmokeForTarget(target, out Thing cover, out float smoke);
            var shooterHeight         = CE_Utility.GetBoundsFor(caster).max.y;
            var coverHeight           = CE_Utility.GetBoundsFor(cover).max.y;
            var centerOfVisibleTarget = (CE_Utility.GetBoundsFor(target.Thing).max.y - coverHeight) / 2 + coverHeight;

            if (centerOfVisibleTarget > shotHeight)
            {
                if (centerOfVisibleTarget > shooterHeight)
                {
                    centerOfVisibleTarget = shooterHeight;
                }
                float distance = target.Thing.Position.DistanceTo(caster.Position);
                // float wobble = Mathf.Atan2(UnityEngine.Random.Range(shotHeight-centerOfVisibleTarget, centerOfVisibleTarget - shotHeight), distance);
                float triangleHeight = centerOfVisibleTarget - shotHeight;
                float wobble         = -Mathf.Atan2(triangleHeight, distance);
                // TODO: Add inaccuracy for not standing in as natural a position
                shotHeight = centerOfVisibleTarget;
                return(wobble);
            }
            return(0);
        }
Exemple #2
0
        private void ApplySuppression(Pawn pawn)
        {
            ShieldBelt shield = null;

            if (pawn.RaceProps.Humanlike)
            {
                // check for shield user

                List <Apparel> wornApparel = pawn.apparel.WornApparel;
                for (int i = 0; i < wornApparel.Count; i++)
                {
                    var personalShield = wornApparel[i] as ShieldBelt;
                    if (personalShield != null)
                    {
                        shield = personalShield;
                        break;
                    }
                }
            }
            //Add suppression
            CompSuppressable compSuppressable = pawn.TryGetComp <CompSuppressable>();

            if (compSuppressable != null &&
                pawn.Faction != launcher?.Faction &&
                (shield == null || shield?.ShieldState == ShieldState.Resetting))
            {
                suppressionAmount = def.projectile.GetDamageAmount(CE_Utility.GetWeaponFromLauncher(launcher));
                var   propsCE           = def.projectile as ProjectilePropertiesCE;
                float penetrationAmount = propsCE == null ? 0f : propsCE.armorPenetration;
                suppressionAmount *= 1 - Mathf.Clamp(compSuppressable.ParentArmor - penetrationAmount, 0, 1);
                compSuppressable.AddSuppression(suppressionAmount, OriginIV3);
            }
        }
Exemple #3
0
 protected override bool TryCastShot()
 {
     //Reduce ammunition
     if (compAmmo != null)
     {
         if (!compAmmo.TryReduceAmmoCount())
         {
             if (compAmmo.hasMagazine)
             {
                 compAmmo.TryStartReload();
             }
             return(false);
         }
     }
     if (base.TryCastShot())
     {
         //Drop casings
         if (verbPropsCE.ejectsCasings && projectilePropsCE.dropsCasings)
         {
             CE_Utility.ThrowEmptyCasing(this.caster.DrawPos, caster.Map, ThingDef.Named(this.projectilePropsCE.casingMoteDefname));
         }
         return(true);
     }
     return(false);
 }
        private bool TryCollideWithRoof(IntVec3 cell)
        {
            if (!cell.Roofed(Map))
            {
                return(false);
            }

            var bounds = CE_Utility.GetBoundsFor(cell, cell.GetRoof(Map));

            float dist;

            if (!bounds.IntersectRay(ShotLine, out dist))
            {
                return(false);
            }
            if (dist * dist > ExactMinusLastPos.sqrMagnitude)
            {
                return(false);
            }

            var point = ShotLine.GetPoint(dist);

            ExactPosition = point;
            landed        = true;

            if (DebugViewSettings.drawInterceptChecks)
            {
                MoteMaker.ThrowText(cell.ToVector3Shifted(), Map, "x", Color.red);
            }

            Impact(null);
            return(true);
        }
Exemple #5
0
 protected override bool TryCastShot()
 {
     //Reduce ammunition
     if (CompAmmo != null)
     {
         if (!CompAmmo.TryReduceAmmoCount())
         {
             if (CompAmmo.HasMagazine)
             {
                 CompAmmo.TryStartReload();
             }
             return(false);
         }
     }
     if (base.TryCastShot())
     {
         //Required since Verb_Shoot does this but Verb_LaunchProjectileCE doesn't when calling base.TryCastShot() because Shoot isn't its base
         if (ShooterPawn != null)
         {
             ShooterPawn.records.Increment(RecordDefOf.ShotsFired);
         }
         //Drop casings
         if (VerbPropsCE.ejectsCasings && projectilePropsCE.dropsCasings)
         {
             CE_Utility.ThrowEmptyCasing(caster.DrawPos, caster.Map, ThingDef.Named(projectilePropsCE.casingMoteDefname));
         }
         // This needs to here for weapons without magazine to ensure their last shot plays sounds
         if (CompAmmo != null && !CompAmmo.HasMagazine && CompAmmo.UseAmmo)
         {
             if (!CompAmmo.Notify_ShotFired())
             {
                 if (VerbPropsCE.muzzleFlashScale > 0.01f)
                 {
                     MoteMaker.MakeStaticMote(caster.Position, caster.Map, ThingDefOf.Mote_ShotFlash, VerbPropsCE.muzzleFlashScale);
                 }
                 if (VerbPropsCE.soundCast != null)
                 {
                     VerbPropsCE.soundCast.PlayOneShot(new TargetInfo(caster.Position, caster.Map));
                 }
                 if (VerbPropsCE.soundCastTail != null)
                 {
                     VerbPropsCE.soundCastTail.PlayOneShotOnCamera();
                 }
                 if (ShooterPawn != null)
                 {
                     if (ShooterPawn.thinker != null)
                     {
                         ShooterPawn.mindState.lastEngageTargetTick = Find.TickManager.TicksGame;
                     }
                 }
             }
             return(CompAmmo.Notify_PostShotFired());
         }
         return(true);
     }
     return(false);
 }
        /// <summary>
        /// Tries to impact the thing based on whether it intersects the given flight path. Trees have RNG chance to not collide even on intersection.
        /// </summary>
        /// <param name="thing">What to impact</param>
        /// <returns>True if impact occured, false otherwise</returns>
        private bool TryCollideWith(Thing thing)
        {
            if (thing == launcher && !canTargetSelf)
            {
                return(false);
            }

            var   bounds = CE_Utility.GetBoundsFor(thing);
            float dist;

            if (!bounds.IntersectRay(ShotLine, out dist))
            {
                return(false);
            }
            if (dist * dist > ExactMinusLastPos.sqrMagnitude)
            {
                return(false);
            }

            // Trees and bushes have RNG chance to collide
            var plant = thing as Plant;

            if (plant != null)
            {
                //TODO: Remove fillPercent dependency because all fillPercents on trees are 0.25
                //Prevents trees near the shooter (e.g the shooter's cover) to be hit
                float chance = thing.def.fillPercent * ((thing.Position - OriginIV3).LengthHorizontal / 40);
                if (Controller.settings.DebugShowTreeCollisionChance)
                {
                    MoteMaker.ThrowText(thing.Position.ToVector3Shifted(), thing.Map, chance.ToString());
                }
                if (!Rand.Chance(chance))
                {
                    return(false);
                }
            }

            var point = ShotLine.GetPoint(dist);

            if (!point.InBounds(this.Map))
            {
                Log.Error("TryCollideWith out of bounds point from ShotLine: obj " + thing.ThingID + ", proj " + this.ThingID + ", dist " + dist + ", point " + point);
            }

            ExactPosition = point;
            landed        = true;

            if (DebugViewSettings.drawInterceptChecks)
            {
                MoteMaker.ThrowText(thing.Position.ToVector3Shifted(), thing.Map, "x", Color.red);
            }

            Impact(thing);
            return(true);
        }
        public static float GetOptimalHuntRange(Pawn hunter, Pawn victim)
        {
            var curJob      = hunter.CurJob;
            var victimProps = victim.RaceProps;

            if (victim.Downed)
            {
                // TODO: How to detect boombats?
                return(Mathf.Min(curJob.verbToUse.verbProps.range, victimProps.executionRange));
            }

            var normalRange = HuntRangePerBodysize(victimProps.baseBodySize, victimProps.executionRange, curJob.verbToUse.verbProps.range);

            if (victimProps.manhunterOnDamageChance > 0)
            {
                // NOTE: I assumed 2 is a good number to consider for aim time of hunting weapons (since hunting weapons are likely to be sniper rifles)
                // We can also get aim time from the actual weapon that the hunter is using
                // but I didn't see the need.
                const int aimTime = 2;

                // Determine how much target can move away from the hunter while the hunter is aiming
                float moveOffset = CE_Utility.GetMoveSpeed(victim) * aimTime;

                // Get a little bit closer when target is small. (Smaller targets don't attack that hard so it's ok from safety standpoint)
                // For reference, Megasloth is 4, and squirrel is 0.2
                float bodySizeFactor = Mathf.Clamp01(victimProps.baseBodySize);

                // If the hunter's shooting skill is below skillThreshold, hunter gets closer to not miss their shots
                // I assumed level 10 is a good enough shooter to stay at maximum distance and don't miss
                const float skillThreshold = 10f;
                float       skillFactor    = Mathf.Clamp01(hunter.skills.GetSkill(SkillDefOf.Shooting).Level / skillThreshold);

                float weaponRange = curJob.verbToUse.verbProps.range;

                // Add additional offset to create a safe margin just in case something weird happens
                const int additionalOffset = 5;
                float     optimal          = weaponRange * bodySizeFactor * skillFactor - moveOffset - additionalOffset;

                // In some cases optimal range gets lower than the normal range that we calculated for non-manhunting animals.
                // For example when the hunter has 0 shooting skill, optimal range will be negative.
                // In that case we just go back to normal calculated range.
                return(Mathf.Max(optimal, normalRange));
            }

            return(normalRange);

            //Fit for an attack range per body size curve.
            float HuntRangePerBodysize(float x, float executionRange, float gunRange)
            {
                return(Mathf.Min(Mathf.Clamp(1 + 20 * (1 - Mathf.Exp(-0.65f * x)), executionRange, 20), gunRange));
            }
        }
        /// <summary>
        /// Tries to impact the thing based on whether it intersects the given flight path. Trees have RNG chance to not collide even on intersection.
        /// </summary>
        /// <param name="thing">What to impact</param>
        /// <returns>True if impact occured, false otherwise</returns>
        private bool TryCollideWith(Thing thing)
        {
            if (thing == launcher && !canTargetSelf)
            {
                return(false);
            }

            var bounds = CE_Utility.GetBoundsFor(thing);

            if (!bounds.IntersectRay(ShotLine, out var dist))
            {
                return(false);
            }
            if (dist * dist > ExactMinusLastPos.sqrMagnitude)
            {
                return(false);
            }

            // Trees and bushes have RNG chance to collide
            if (thing is Plant)
            {
                //Prevents trees near the shooter (e.g the shooter's cover) to be hit
                var accuracyFactor = def.projectile.alwaysFreeIntercept ? 1 : (thing.Position - OriginIV3).LengthHorizontal / 40 * AccuracyFactor;
                var chance         = thing.def.fillPercent * accuracyFactor;
                if (Controller.settings.DebugShowTreeCollisionChance)
                {
                    MoteMaker.ThrowText(thing.Position.ToVector3Shifted(), thing.Map, chance.ToString());
                }
                if (!Rand.Chance(chance))
                {
                    return(false);
                }
            }

            var point = ShotLine.GetPoint(dist);

            if (!point.InBounds(Map))
            {
                Log.Error("TryCollideWith out of bounds point from ShotLine: obj " + thing.ThingID + ", proj " + ThingID + ", dist " + dist + ", point " + point);
            }

            ExactPosition = point;
            landed        = true;

            if (Controller.settings.DebugDrawInterceptChecks)
            {
                MoteMaker.ThrowText(thing.Position.ToVector3Shifted(), thing.Map, "x", Color.red);
            }

            Impact(thing);
            return(true);
        }
Exemple #9
0
        public virtual ShiftVecReport ShiftVecReportFor(LocalTargetInfo target)
        {
            IntVec3        targetCell = target.Cell;
            ShiftVecReport report     = new ShiftVecReport();

            report.target           = target;
            report.aimingAccuracy   = AimingAccuracy;
            report.sightsEfficiency = SightsEfficiency;
            report.shotDist         = (targetCell - caster.Position).LengthHorizontal;
            report.maxRange         = verbProps.range;
            report.lightingShift    = CE_Utility.GetLightingShift(caster, LightingTracker.CombatGlowAtFor(caster.Position, targetCell));

            if (!caster.Position.Roofed(caster.Map) || !targetCell.Roofed(caster.Map))  //Change to more accurate algorithm?
            {
                report.weatherShift = 1 - caster.Map.weatherManager.CurWeatherAccuracyMultiplier;
            }
            report.shotSpeed   = ShotSpeed;
            report.swayDegrees = SwayAmplitude;
            var spreadmult = projectilePropsCE != null ? projectilePropsCE.spreadMult : 0f;

            report.spreadDegrees = (EquipmentSource?.GetStatValue(StatDef.Named("ShotSpread")) ?? 0) * spreadmult;
            Thing cover;
            float smokeDensity;

            GetHighestCoverAndSmokeForTarget(target, out cover, out smokeDensity);
            report.cover        = cover;
            report.smokeDensity = smokeDensity;

            if (Controller.settings.DebugVerbose)
            {
                Log.Message($"<color=red>CE</color>: <color=orange>{caster}</color> shooting <color=orange>{target.Thing}</color> <color=yellow>ShiftVecReport</color>\n" +
                            $"1- aimingAccuracy:{report.aimingAccuracy}\n" +
                            $"2- sightsEfficiency:{report.sightsEfficiency}\n" +
                            $"3- maxRange:{report.maxRange}\n" +
                            $"4- lightingShift:{report.lightingShift}\n" +
                            $"5- spreadDegrees:{report.spreadDegrees}\n" +
                            $"6- smokeDensity:{report.smokeDensity}\n" +
                            $"7- swayDegrees:{report.swayDegrees}\n" +
                            $"8- shotSpeed:{report.shotSpeed}\n" +
                            $"9- shotDist:{report.shotDist}\n");
            }
            return(report);
        }
Exemple #10
0
 public bool GetChargeBracket(float range, float shotHeight, float gravityFactor, out Vector2 bracket)
 {
     bracket = new Vector2(0, 0);
     if (Props.chargeSpeeds.Count <= 0)
     {
         Log.Error("Tried getting charge bracket from empty list.");
         return(false);
     }
     foreach (var speed in Props.chargeSpeeds)
     {
         var curRange = CE_Utility.MaxProjectileRange(shotHeight, speed, MaxRangeAngle, gravityFactor);
         if (range <= curRange)
         {
             bracket = new Vector2(speed, curRange);
             return(true);
         }
     }
     return(false);
 }
        /// <summary>
        /// Checks if the shooter can hit the target from a certain position with regards to cover height
        /// </summary>
        /// <param name="root">The position from which to check</param>
        /// <param name="targ">The target to check for line of sight</param>
        /// <returns>True if shooter can hit target from root position, false otherwise</returns>
        public override bool CanHitTargetFrom(IntVec3 root, LocalTargetInfo targ)
        {
            //Sanity check for flyOverhead projectiles, they should not attack things under thick roofs
            if (projectileDef.projectile.flyOverhead)
            {
                RoofDef roofDef = caster.Map.roofGrid.RoofAt(targ.Cell);
                if (roofDef != null && roofDef.isThickRoof)
                {
                    return(false);
                }
                return(base.CanHitTargetFrom(root, targ));
            }

            if (base.CanHitTargetFrom(root, targ))
            {
                //Check if target is obstructed behind cover
                Thing coverTarg;
                if (this.GetPartialCoverBetween(root.ToVector3Shifted(), targ.Cell.ToVector3Shifted(), out coverTarg))
                {
                    float targetHeight = CE_Utility.GetCollisionHeight(targ.Thing);
                    if (targetHeight <= CE_Utility.GetCollisionHeight(coverTarg))
                    {
                        return(false);
                    }
                }
                //Check if shooter is obstructed by cover
                Thing coverShoot;
                if (this.GetPartialCoverBetween(targ.Cell.ToVector3Shifted(), root.ToVector3Shifted(), out coverShoot))
                {
                    float shotHeight = CE_Utility.GetCollisionHeight(this.caster);
                    if (this.CasterPawn != null)
                    {
                        shotHeight *= shotHeightFactor;
                    }
                    if (shotHeight <= CE_Utility.GetCollisionHeight(coverShoot))
                    {
                        return(false);
                    }
                }
                return(true);
            }
            return(false);
        }
Exemple #12
0
        protected virtual void Explode()
        {
            ExplosionCE explosion = GenSpawn.Spawn(CE_ThingDefOf.ExplosionCE, ExactPosition.ToIntVec3(), Map) as ExplosionCE;

            explosion.height     = ExactPosition.y;
            explosion.radius     = def.projectile.explosionRadius;
            explosion.damType    = def.projectile.damageDef;
            explosion.instigator = launcher;
            explosion.damAmount  = def.projectile.GetDamageAmount(CE_Utility.GetWeaponFromLauncher(launcher));
            explosion.weapon     = equipmentDef;
            explosion.projectile = def;
            explosion.preExplosionSpawnThingDef            = def.projectile.preExplosionSpawnThingDef;
            explosion.preExplosionSpawnChance              = def.projectile.preExplosionSpawnChance;
            explosion.preExplosionSpawnThingCount          = def.projectile.preExplosionSpawnThingCount;
            explosion.postExplosionSpawnThingDef           = def.projectile.postExplosionSpawnThingDef;
            explosion.postExplosionSpawnChance             = def.projectile.postExplosionSpawnChance;
            explosion.postExplosionSpawnThingCount         = def.projectile.postExplosionSpawnThingCount;
            explosion.applyDamageToExplosionCellsNeighbors = def.projectile.applyDamageToExplosionCellsNeighbors;
            explosion.chanceToStartFire = def.projectile.explosionChanceToStartFire;
            explosion.damageFalloff     = def.projectile.explosionDamageFalloff;
            explosion.StartExplosion(def.projectile.soundExplode);

            if (this.def.projectile.explosionEffect != null)
            {
                Effecter effecter = this.def.projectile.explosionEffect.Spawn();
                effecter.Trigger(new TargetInfo(ExactPosition.ToIntVec3(), Map, false), new TargetInfo(ExactPosition.ToIntVec3(), Map, false));
                effecter.Cleanup();
            }

            //This code was disabled because it didn't run under previous circumstances. Could be enabled if necessary

            /*
             * if (map != null && base.ExactPosition.ToIntVec3().IsValid)
             * {
             *  ThrowBigExplode(base.ExactPosition + Gen.RandomHorizontalVector(def.projectile.explosionRadius * 0.5f), base.Map, def.projectile.explosionRadius * 0.4f);
             * }
             */

            base.Impact(null); // base.Impact() handles this.Destroy() and comp.Explode()
        }
        /// <summary>
        /// Tries to impact the thing based on whether it intersects the given flight path. Trees have RNG chance to not collide even on intersection.
        /// </summary>
        /// <param name="thing">What to impact</param>
        /// <param name="shotLine">Projectile's path of travel</param>
        /// <returns>True if impact occured, false otherwise</returns>
        private bool TryCollideWith(Thing thing, Ray shotLine)
        {
            if (thing == launcher && !canTargetSelf)
            {
                return(false);
            }

            // Trees have RNG chance to collide
            if (thing.IsTree())
            {
                float chance = thing.def.fillPercent * ((thing.Position - OriginIV3).LengthHorizontal / 40);
                if (Controller.settings.DebugShowTreeCollisionChance)
                {
                    MoteMaker.ThrowText(thing.Position.ToVector3Shifted(), thing.Map, chance.ToString());
                }
                if (!Rand.Chance(chance))
                {
                    return(false);
                }
            }
            else
            {
                var bounds = CE_Utility.GetBoundsFor(thing);
                if (!bounds.IntersectRay(shotLine))
                {
                    return(false);
                }
            }


            if (DebugViewSettings.drawInterceptChecks)
            {
                MoteMaker.ThrowText(thing.Position.ToVector3Shifted(), thing.Map, "x", Color.red);
            }
            Impact(thing);
            return(true);
        }
        public override void CompTick()
        {
            base.CompTick();

            // Update suppressed tick counter and check for mental breaks
            if (!isSuppressed)
            {
                ticksHunkered = 0;
            }
            else if (IsHunkering)
            {
                ticksHunkered++;
            }

            if (ticksHunkered > MinTicksUntilMentalBreak && Rand.Chance(ChanceBreakPerTick))
            {
                var pawn = (Pawn)parent;
                if (pawn.mindState != null && !pawn.mindState.mentalStateHandler.InMentalState)
                {
                    var possibleBreaks = SuppressionUtility.GetPossibleBreaks(pawn);
                    if (possibleBreaks.Any())
                    {
                        pawn.mindState.mentalStateHandler.TryStartMentalState(possibleBreaks.RandomElement());
                    }
                }
            }

            //Apply decay once per second
            if (ticksUntilDecay > 0)
            {
                ticksUntilDecay--;
            }
            else if (currentSuppression > 0)
            {
                //Decay global suppression
                if (Controller.settings.DebugShowSuppressionBuildup && Gen.IsHashIntervalTick(parent, 30))
                {
                    MoteMaker.ThrowText(parent.DrawPos, parent.Map, "-" + (SuppressionDecayRate * 30), Color.red);
                }
                currentSuppression -= Mathf.Min(SuppressionDecayRate, currentSuppression);
                isSuppressed        = currentSuppression > 0;

                // Clear crouch-walking
                if (!isSuppressed)
                {
                    isCrouchWalking = false;
                }

                //Decay location suppression
                locSuppressionAmount -= Mathf.Min(SuppressionDecayRate, locSuppressionAmount);
            }

            // Throw mote at set interval
            if (parent.IsHashIntervalTick(TicksPerMote) && CanReactToSuppression)
            {
                if (this.IsHunkering)
                {
                    CE_Utility.MakeIconOverlay((Pawn)parent, CE_ThingDefOf.Mote_HunkerIcon);
                }
                else if (this.isSuppressed)
                {
                    CE_Utility.MakeIconOverlay((Pawn)parent, CE_ThingDefOf.Mote_SuppressIcon);
                }
            }
        }
        // Added targetThing to parameters so we can calculate its height
        private bool CanHitCellFromCellIgnoringRange(IntVec3 sourceSq, IntVec3 targetLoc, Thing targetThing = null, bool includeCorners = false)
        {
            // Vanilla checks
            if (this.verbProps.mustCastOnOpenGround && (!targetLoc.Standable(this.caster.Map) || this.caster.Map.thingGrid.CellContains(targetLoc, ThingCategory.Pawn)))
            {
                return(false);
            }
            if (this.verbProps.requireLineOfSight)
            {
                // Calculate shot vector
                Vector3 shotSource = ShotSource;

                Vector3 targetPos;
                if (targetThing != null)
                {
                    Vector3 targDrawPos = targetThing.DrawPos;
                    targetPos = new Vector3(targDrawPos.x, new CollisionVertical(targetThing).Max, targDrawPos.z);
                }
                else
                {
                    targetPos = targetLoc.ToVector3Shifted();
                }
                Ray shotLine = new Ray(shotSource, (targetPos - shotSource));

                // Create validator to check for intersection with partial cover
                var aimMode = CompFireModes?.CurrentAimMode;
                Func <IntVec3, bool> validator = delegate(IntVec3 cell)
                {
                    // Skip this check entirely if we're doing suppressive fire and cell is adjacent to target
                    if (VerbPropsCE.ignorePartialLoSBlocker || aimMode == AimMode.SuppressFire)
                    {
                        return(true);
                    }

                    Thing cover = cell.GetFirstPawn(caster.Map);
                    if (cover == null)
                    {
                        cover = cell.GetCover(caster.Map);
                    }
                    if (cover != null && !cover.IsTree() && !cover.Position.AdjacentTo8Way(sourceSq))
                    {
                        Bounds bounds = CE_Utility.GetBoundsFor(cover);

                        // Check for intersect
                        if (bounds.IntersectRay(shotLine))
                        {
                            if (Controller.settings.DebugDrawPartialLoSChecks)
                            {
                                caster.Map.debugDrawer.FlashCell(cell, 0, bounds.size.y.ToString());
                            }
                            return(false);
                        }
                        else if (Controller.settings.DebugDrawPartialLoSChecks)
                        {
                            caster.Map.debugDrawer.FlashCell(cell, 0.7f, bounds.size.y.ToString());
                        }
                    }
                    return(true);
                };
                // Add validator to parameters
                if (!includeCorners)
                {
                    if (!GenSight.LineOfSight(sourceSq, targetLoc, this.caster.Map, true, validator, 0, 0))
                    {
                        return(false);
                    }
                }
                else if (!GenSight.LineOfSightToEdges(sourceSq, targetLoc, this.caster.Map, true, validator))
                {
                    return(false);
                }
            }
            return(true);
        }
        // Added targetThing to parameters so we can calculate its height
        private bool CanHitCellFromCellIgnoringRange(IntVec3 sourceSq, IntVec3 rootCell, IntVec3 targetLoc, Thing targetThing = null, bool includeCorners = false)
        {
            // Vanilla checks
            if (this.verbProps.mustCastOnOpenGround && (!targetLoc.Standable(this.caster.Map) || this.caster.Map.thingGrid.CellContains(targetLoc, ThingCategory.Pawn)))
            {
                return(false);
            }
            if (this.verbProps.requireLineOfSight)
            {
                // Calculate shot vector
                Vector3 shotSource = ShotSource;

                Vector3 targetPos;
                if (targetThing != null)
                {
                    Vector3 targDrawPos = targetThing.DrawPos;
                    targetPos = new Vector3(targDrawPos.x, new CollisionVertical(targetThing).Max, targDrawPos.z);
                    var targPawn = targetThing as Pawn;
                    if (targPawn != null)
                    {
                        targetPos += targPawn.Drawer.leaner.LeanOffset * 0.5f;
                    }
                }
                else
                {
                    targetPos = targetLoc.ToVector3Shifted();
                }
                Ray shotLine = new Ray(shotSource, (targetPos - shotSource));

                // Create validator to check for intersection with partial cover
                var aimMode = CompFireModes?.CurrentAimMode;
                Func <IntVec3, bool> validator = delegate(IntVec3 cell)
                {
                    // Skip this check entirely if we're doing suppressive fire and cell is adjacent to target
                    if (VerbPropsCE.ignorePartialLoSBlocker || aimMode == AimMode.SuppressFire)
                    {
                        return(true);
                    }

                    Thing cover = cell.GetFirstPawn(caster.Map);

                    if (cover == null)
                    {
                        cover = cell.GetCover(caster.Map);
                    }

                    if (cover != null && cover != ShooterPawn && cover != caster && cover != targetThing && !cover.IsPlant())
                    {
                        //    var tr = cover.Position.AdjacentTo8Way(sourceSq);
                        //    Log.Message("cover: " + cover.ToString() + " value: " + tr.ToString());

                        if (VerbPropsCE.verbClass == typeof(Verb_ShootCEOneUse) && cover.def.fillPercent < 0.8)
                        {
                            return(true);
                        }

                        Bounds bounds = CE_Utility.GetBoundsFor(cover);
                        // Check for intersect
                        if (bounds.IntersectRay(shotLine))
                        {
                            if (Controller.settings.DebugDrawPartialLoSChecks)
                            {
                                caster.Map.debugDrawer.FlashCell(cell, 0, bounds.size.y.ToString());
                            }
                            return(false);
                        }
                        else if (Controller.settings.DebugDrawPartialLoSChecks)
                        {
                            caster.Map.debugDrawer.FlashCell(cell, 0.7f, bounds.size.y.ToString());
                        }

                        if (cover.def.fillPercent > 0.99)
                        {
                            return(false);
                        }
                    }
                    return(true);
                };
                // Add validator to parameters

                /*
                 * if (!includeCorners)
                 * {
                 *  if (!GenSight.LineOfSight(sourceSq, targetLoc, this.caster.Map, true, validator, 0, 0))
                 *  {
                 *      Log.Message("4.1 CanHitCellFromCellIgnoringRange: false");
                 *      return false;
                 *  }
                 * }
                 * else if (!GenSight.LineOfSightToEdges(sourceSq, targetLoc, this.caster.Map, true, validator))
                 * {
                 *  Log.Message("4.2 CanHitCellFromCellIgnoringRange: false");
                 *  return false;
                 * }
                 */
                var exactTargetSq = targetPos.ToIntVec3();
                foreach (IntVec3 curCell in GenSight.PointsOnLineOfSight(sourceSq, exactTargetSq))
                {
                    if (curCell != sourceSq && curCell != exactTargetSq && !validator(curCell))
                    {
                        return(false);
                    }
                }
                int c = 0;
                foreach (IntVec3 curCell2 in GenSight.PointsOnLineOfSight(rootCell, exactTargetSq))
                {
                    if (curCell2 != rootCell && curCell2 != exactTargetSq && !validator(curCell2))
                    {
                        c++;
                    }
                    if (c >= 2)
                    {
                        if (rootCell.z == exactTargetSq.z || rootCell.x == exactTargetSq.x)
                        {
                            return(false);
                        }
                    }
                }
            }
            return(true);
        }
        /// <summary>
        /// Shifts the original target position in accordance with target leading, range estimation and weather/lighting effects
        /// </summary>
        protected virtual Vector3 ShiftTarget(ShiftVecReport report)
        {
            // ----------------------------------- STEP 0: Actual location

            Vector3 targetLoc = report.targetPawn != null?Vector3.Scale(report.targetPawn.DrawPos, new Vector3(1, 0, 1)) : report.target.Cell.ToVector3Shifted();

            Vector3 sourceLoc = this.CasterPawn != null?Vector3.Scale(this.CasterPawn.DrawPos, new Vector3(1, 0, 1)) : this.caster.Position.ToVector3Shifted();

            // ----------------------------------- STEP 1: Shift for visibility

            Vector2 circularShiftVec = report.GetRandCircularVec();
            Vector3 newTargetLoc     = targetLoc;

            newTargetLoc.x += circularShiftVec.x;
            newTargetLoc.z += circularShiftVec.y;

            // ----------------------------------- STEP 2: Estimated shot to hit location

            // On first shot of burst do a range estimate
            if (this.estimatedTargDist < 0)
            {
                this.estimatedTargDist = report.GetRandDist();
            }
            newTargetLoc = sourceLoc + (newTargetLoc - sourceLoc).normalized * this.estimatedTargDist;

            // Lead a moving target
            newTargetLoc += report.GetRandLeadVec();

            // ----------------------------------- STEP 3: Recoil, Skewing, Skill checks, Cover calculations

            Vector2 skewVec = new Vector2(0, 0);

            skewVec += this.GetSwayVec();
            skewVec += this.GetRecoilVec();

            // Height difference calculations for ShotAngle
            float heightDifference = 0;
            float targetableHeight = 0;

            // Projectiles with flyOverhead target the ground below the target and ignore cover
            if (!projectileDef.projectile.flyOverhead)
            {
                targetableHeight = CE_Utility.GetCollisionHeight(this.currentTarget.Thing);
                if (report.cover != null)
                {
                    targetableHeight += CE_Utility.GetCollisionHeight(report.cover);
                }
                heightDifference += targetableHeight * 0.5f;    //Optimal hit level is halfway
            }

            this.shotHeight = CE_Utility.GetCollisionHeight(this.caster);
            if (this.CasterPawn != null)
            {
                this.shotHeight *= shotHeightFactor;
            }
            heightDifference -= this.shotHeight;
            skewVec          += new Vector2(0, GetShotAngle(this.shotSpeed, (newTargetLoc - sourceLoc).magnitude, heightDifference) * (180 / (float)Math.PI));

            // ----------------------------------- STEP 4: Mechanical variation

            // Get shotvariation
            Vector2 spreadVec = report.GetRandSpreadVec();

            skewVec += spreadVec;

            // Skewing		-		Applied after the leading calculations to not screw them up
            float distanceTraveled = GetDistanceTraveled(this.shotSpeed, (float)(skewVec.y * (Math.PI / 180)), this.shotHeight);

            newTargetLoc = sourceLoc + ((newTargetLoc - sourceLoc).normalized * distanceTraveled);
            newTargetLoc = sourceLoc + (Quaternion.AngleAxis(skewVec.x, Vector3.up) * (newTargetLoc - sourceLoc));

            this.shotAngle = (float)(skewVec.y * (Math.PI / 180));

            return(newTargetLoc);
        }
Exemple #18
0
        /// <summary>
        /// Tries to impact the thing based on whether it intersects the given flight path. Trees have RNG chance to not collide even on intersection.
        /// </summary>
        /// <param name="thing">What to impact</param>
        /// <returns>True if impact occured, false otherwise</returns>
        private bool TryCollideWith(Thing thing)
        {
            if (thing == launcher && !canTargetSelf)
            {
                return(false);
            }

            var   bounds = CE_Utility.GetBoundsFor(thing);
            float dist;

            if (!bounds.IntersectRay(ShotLine, out dist))
            {
                return(false);
            }
            if (dist * dist > ExactMinusLastPos.sqrMagnitude)
            {
                return(false);
            }

            // Trees and bushes have RNG chance to collide
            var plant = thing as Plant;

            if (plant != null)
            {
                //TODO: Remove fillPercent dependency because all fillPercents on trees are 0.25
                //Prevents trees near the shooter (e.g the shooter's cover) to be hit

                //Dependence to catch the tree from armorPenetration. Large calibers have less chance.
                var   propsCE               = def.projectile as ProjectilePropertiesCE;
                float penetrationAmount     = propsCE == null ? 0.1f : propsCE.armorPenetration;                            //Every projectile, which not use flyoverhead, require armorPenetration stat for calculating collision.
                float penetrationmultiplier = Mathf.Clamp(((thing.def.fillPercent * 3.2f) - penetrationAmount), 0.05f, 1f); // 2.5-3.5 good values for 0.20-0.30 fillpercent.
                float rangemultiplier       = ((thing.Position - OriginIV3).LengthHorizontal / 15);                         // 10-20 is fine for prevent to shoot near tree.
                float chance = penetrationmultiplier * (rangemultiplier < 1f ? rangemultiplier : 1f);                       // when projectile reach 15 cells distance, we set limit for multiplier bcs chances to hit greatly increased.
                if (Controller.settings.DebugShowTreeCollisionChance)
                {
                    MoteMaker.ThrowText(thing.Position.ToVector3Shifted(), thing.Map, chance.ToString());
                }
                if (!Rand.Chance(chance))
                {
                    return(false);
                }
            }

            var point = ShotLine.GetPoint(dist);

            if (!point.InBounds(this.Map))
            {
                Log.Error("TryCollideWith out of bounds point from ShotLine: obj " + thing.ThingID + ", proj " + this.ThingID + ", dist " + dist + ", point " + point);
            }

            ExactPosition = point;

            if (intendedTarget != null)
            {
                var fulldistance         = (intendedTarget.Position - OriginIV3).LengthHorizontalSquared;
                var traveleddistance     = (thing.Position - OriginIV3).LengthHorizontalSquared;
                var requireddisttotarget = fulldistance - traveleddistance;
                if (thing is Building && thing.def.fillPercent > 0.5f)
                {
                    //    Log.Message("fulldist: " + fulldistance.ToString() + " traveled: " + traveleddistance.ToString() + " required: " + requireddisttotarget.ToString());
                    if (traveleddistance < requireddisttotarget)
                    {
                        return(false);
                    }
                }
            }

            landed = true;

            //    if (Controller.settings.DebugDrawInterceptChecks) MoteMaker.ThrowText(thing.Position.ToVector3Shifted(), thing.Map, "x", Color.red);

            Impact(thing);
            return(true);
        }
Exemple #19
0
        private static void CalculateHeightRange(Thing thing, out FloatRange heightRange, out float shotHeight)
        {
            shotHeight  = 0;
            heightRange = new FloatRange(0, 0);
            if (thing == null)
            {
                return;
            }

            var plant = thing as Plant;

            if (plant != null)
            {
                //Height matches up exactly with visual size
                heightRange = new FloatRange(0f, BoundsInjector.ForPlant(plant).y);
                return;
            }

            if (thing is Building)
            {
                if (thing is Building_Door door && door.Open)
                {
                    return;             //returns heightRange = (0,0) & shotHeight = 0. If not open, doors have FillCategory.Full so returns (0, WallCollisionHeight)
                }

                if (thing.def.Fillage == FillCategory.Full)
                {
                    heightRange = new FloatRange(0, WallCollisionHeight);
                    shotHeight  = WallCollisionHeight;
                    return;
                }
                float fillPercent = thing.def.fillPercent;
                heightRange = new FloatRange(Mathf.Min(0f, fillPercent), Mathf.Max(0f, fillPercent));
                shotHeight  = fillPercent;
                return;
            }

            float collisionHeight  = 0f;
            float shotHeightOffset = 0;
            var   pawn             = thing as Pawn;

            if (pawn != null)
            {
                collisionHeight = CE_Utility.GetCollisionBodyFactors(pawn).y;

                shotHeightOffset = collisionHeight * (1 - BodyRegionMiddleHeight);

                // Humanlikes in combat crouch to reduce their profile
                if (pawn.IsCrouching())
                {
                    float crouchHeight = BodyRegionBottomHeight * collisionHeight;  // Minimum height we can crouch down to

                    // Find the highest adjacent cover
                    Map map = pawn.Map;
                    foreach (IntVec3 curCell in GenAdjFast.AdjacentCells8Way(pawn.Position))
                    {
                        if (curCell.InBounds(map))
                        {
                            Thing cover = curCell.GetCover(map);
                            if (cover != null && cover.def.Fillage == FillCategory.Partial && !cover.IsPlant())
                            {
                                var coverHeight = new CollisionVertical(cover).Max;
                                if (coverHeight > crouchHeight)
                                {
                                    crouchHeight = coverHeight;
                                }
                            }
                        }
                    }
                    collisionHeight = Mathf.Min(collisionHeight, crouchHeight + 0.01f + shotHeightOffset);  // We crouch down only so far that we can still shoot over our own cover and never beyond our own body size
                }
            }
            else
            {
                collisionHeight = thing.def.fillPercent;
            }
            var edificeHeight = 0f;

            if (thing.Map != null)
            {
                var edifice = thing.Position.GetCover(thing.Map);
                if (edifice != null && edifice.GetHashCode() != thing.GetHashCode() && !edifice.IsPlant())
                {
                    edificeHeight = new CollisionVertical(edifice).heightRange.max;
                }
            }
            float fillPercent2 = collisionHeight;

            heightRange = new FloatRange(Mathf.Min(edificeHeight, edificeHeight + fillPercent2), Mathf.Max(edificeHeight, edificeHeight + fillPercent2));
            shotHeight  = heightRange.max - shotHeightOffset;
        }
Exemple #20
0
        public string GetTextReadout()
        {
            StringBuilder stringBuilder = new StringBuilder();

            if (visibilityShift > 0)
            {
                stringBuilder.AppendLine("   " + "CE_VisibilityError".Translate() + "\t" + GenText.ToStringByStyle(visibilityShift, ToStringStyle.FloatTwo) + " c");

                if (lightingShift > 0)
                {
                    stringBuilder.AppendLine("      " + "Darkness".Translate() + "\t" + AsPercent(lightingShift));
                }
                if (weatherShift > 0)
                {
                    stringBuilder.AppendLine("      " + "Weather".Translate() + "\t" + AsPercent(weatherShift));
                }
                if (smokeDensity > 0)
                {
                    stringBuilder.AppendLine("      " + "CE_SmokeDensity".Translate() + "\t" + AsPercent(smokeDensity));
                }
            }
            if (leadShift > 0)
            {
                stringBuilder.AppendLine("   " + "CE_LeadError".Translate() + "\t" + GenText.ToStringByStyle(leadShift, ToStringStyle.FloatTwo) + " c");
            }
            if (distShift > 0)
            {
                stringBuilder.AppendLine("   " + "CE_RangeError".Translate() + "\t" + GenText.ToStringByStyle(distShift, ToStringStyle.FloatTwo) + " c");
            }
            if (swayDegrees > 0)
            {
                stringBuilder.AppendLine("   " + "CE_Sway".Translate() + "\t\t" + GenText.ToStringByStyle(swayDegrees, ToStringStyle.FloatTwo) + "°");
            }
            if (spreadDegrees > 0)
            {
                stringBuilder.AppendLine("   " + "CE_Spread".Translate() + "\t\t" + GenText.ToStringByStyle(spreadDegrees, ToStringStyle.FloatTwo) + "°");
            }
            // Don't display cover and target size if our weapon has a CEP
            if (circularMissRadius > 0)
            {
                stringBuilder.AppendLine("   " + "CE_MissRadius".Translate() + "\t" + GenText.ToStringByStyle(circularMissRadius, ToStringStyle.FloatTwo) + " c");
                if (indirectFireShift > 0)
                {
                    stringBuilder.AppendLine("   " + "CE_IndirectFire".Translate() + "\t" + GenText.ToStringByStyle(indirectFireShift, ToStringStyle.FloatTwo) + " c");
                    PlayerKnowledgeDatabase.KnowledgeDemonstrated(CE_ConceptDefOf.CE_MortarDirectFire, KnowledgeAmount.FrameDisplayed); // Show we learned about indirect fire penalties
                }
            }
            else
            {
                if (cover != null)
                {
                    stringBuilder.AppendLine("   " + "CE_CoverHeight".Translate() + "\t" + new CollisionVertical(cover).Max + " c");
                }
                if (target.Thing != null)
                {
                    stringBuilder.AppendLine("   " + "CE_TargetHeight".Translate() + "\t" + GenText.ToStringByStyle(new CollisionVertical(target.Thing).HeightRange.Span, ToStringStyle.FloatTwo) + " c");
                    stringBuilder.AppendLine("   " + "CE_TargetWidth".Translate() + "\t" + GenText.ToStringByStyle(CE_Utility.GetCollisionWidth(target.Thing), ToStringStyle.FloatTwo) + " c");
                    var pawn = target.Thing as Pawn;
                    if (pawn != null && pawn.IsCrouching())
                    {
                        LessonAutoActivator.TeachOpportunity(CE_ConceptDefOf.CE_Crouching, OpportunityType.GoodToKnow);
                    }
                }
                PlayerKnowledgeDatabase.KnowledgeDemonstrated(CE_ConceptDefOf.CE_AimingSystem, KnowledgeAmount.FrameDisplayed); // Show we learned about the aiming system
            }
            return(stringBuilder.ToString());
        }
Exemple #21
0
        public Vector2 GetRandCircularVec()
        {
            Vector2 vec = CE_Utility.GenRandInCircle(visibilityShift + circularMissRadius + indirectFireShift);

            return(vec);
        }
Exemple #22
0
        public virtual void RayCast(Thing launcher, VerbProperties verbProps, Vector2 origin, float shotAngle, float shotRotation, float shotHeight = 0f, float shotSpeed = -1f, float spreadDegrees = 0f, float aperatureSize = 0.03f, Thing equipment = null)
        {
            float magicSpreadFactor        = Mathf.Sin(0.06f / 2 * Mathf.Deg2Rad) + aperatureSize;
            float magicLaserDamageConstant = 1 / (magicSpreadFactor * magicSpreadFactor * 3.14159f);

            ProjectilePropertiesCE pprops = def.projectile as ProjectilePropertiesCE;

            shotRotation = Mathf.Deg2Rad * shotRotation + (float)(3.14159 / 2.0f);
            Vector3 direction   = new Vector3(Mathf.Cos(shotRotation) * Mathf.Cos(shotAngle), Mathf.Sin(shotAngle), Mathf.Sin(shotRotation) * Mathf.Cos(shotAngle));
            Vector3 origin3     = new Vector3(origin.x, shotHeight, origin.y);
            Map     map         = launcher.Map;
            Vector3 destination = direction * verbProps.range + origin3;

            this.shotAngle    = shotAngle;
            this.shotHeight   = shotHeight;
            this.shotRotation = shotRotation;
            this.launcher     = launcher;
            this.origin       = origin;
            equipmentDef      = equipment?.def ?? null;
            Ray   ray          = new Ray(origin3, direction);
            var   lbce         = this as LaserBeamCE;
            float spreadRadius = Mathf.Sin(spreadDegrees / 2.0f * Mathf.Deg2Rad);

            LaserGunDef defWeapon = equipmentDef as LaserGunDef;
            Vector3     muzzle    = ray.GetPoint((defWeapon == null ? 0.9f : defWeapon.barrelLength));
            var         it_bounds = CE_Utility.GetBoundsFor(intendedTarget);

            for (int i = 1; i < verbProps.range; i++)
            {
                float spreadArea = (i * spreadRadius + aperatureSize) * (i * spreadRadius + aperatureSize) * 3.14159f;
                if (pprops.damageFalloff)
                {
                    lbce.DamageModifier = 1 / (magicLaserDamageConstant * spreadArea);
                }

                Vector3 tp = ray.GetPoint(i);
                if (tp.y > CollisionVertical.WallCollisionHeight)
                {
                    break;
                }
                if (tp.y < 0)
                {
                    destination   = tp;
                    landed        = true;
                    ExactPosition = tp;
                    Position      = ExactPosition.ToIntVec3();
                    break;
                }
                foreach (Thing thing in Map.thingGrid.ThingsListAtFast(tp.ToIntVec3()))
                {
                    if (this == thing)
                    {
                        continue;
                    }
                    var bounds = CE_Utility.GetBoundsFor(thing);
                    if (!bounds.IntersectRay(ray, out var dist))
                    {
                        continue;
                    }
                    if (i < 2 && thing != intendedTarget)
                    {
                        continue;
                    }

                    if (thing is Plant plant)
                    {
                        if (!Rand.Chance(thing.def.fillPercent * plant.Growth))
                        {
                            continue;
                        }
                    }
                    else if (thing is Building)
                    {
                        if (!Rand.Chance(thing.def.fillPercent))
                        {
                            continue;
                        }
                    }
                    ExactPosition = tp;
                    destination   = tp;
                    landed        = true;
                    LastPos       = destination;
                    ExactPosition = destination;
                    Position      = ExactPosition.ToIntVec3();

                    lbce.SpawnBeam(muzzle, destination);

                    lbce.Impact(thing, muzzle);

                    return;
                }
            }
            if (lbce != null)
            {
                lbce.SpawnBeam(muzzle, destination);
                Destroy(DestroyMode.Vanish);
                return;
            }
        }
        protected override void Impact(Thing hitThing)
        {
            Map map = base.Map;
            BattleLogEntry_RangedImpact logEntry = null;

            if (logMisses
                ||
                (!logMisses &&
                 hitThing != null &&
                 (hitThing is Pawn ||
                  hitThing is Building_Turret)
                ))
            {
                LogImpact(hitThing, out logEntry);
            }

            if (hitThing != null)
            {
                // launcher being the pawn equipping the weapon, not the weapon itself
                int damageAmountBase          = def.projectile.GetDamageAmount(CE_Utility.GetWeaponFromLauncher(launcher));
                DamageDefExtensionCE damDefCE = def.projectile.damageDef.GetModExtension <DamageDefExtensionCE>() ?? new DamageDefExtensionCE();
                var projectilePropsCE         = def.projectile as ProjectilePropertiesCE;

                DamageInfo dinfo = new DamageInfo(
                    def.projectile.damageDef,
                    damageAmountBase,
                    projectilePropsCE.armorPenetration, //Armor Penetration
                    ExactRotation.eulerAngles.y,
                    launcher,
                    null,
                    def);

                // Set impact height
                BodyPartDepth partDepth = damDefCE != null && damDefCE.harmOnlyOutsideLayers ? BodyPartDepth.Outside : BodyPartDepth.Undefined;
                //NOTE: ExactPosition.y isn't always Height at the point of Impact!
                BodyPartHeight partHeight = new CollisionVertical(hitThing).GetCollisionBodyHeight(ExactPosition.y);
                dinfo.SetBodyRegion(partHeight, partDepth);
                if (damDefCE != null && damDefCE.harmOnlyOutsideLayers)
                {
                    dinfo.SetBodyRegion(BodyPartHeight.Undefined, BodyPartDepth.Outside);
                }

                // Apply primary damage
                hitThing.TakeDamage(dinfo).AssociateWithLog(logEntry);

                // Apply secondary to non-pawns (pawn secondary damage is handled in the damage worker)

                if (!(hitThing is Pawn) && projectilePropsCE != null && !projectilePropsCE.secondaryDamage.NullOrEmpty())
                {
                    foreach (SecondaryDamage cur in projectilePropsCE.secondaryDamage)
                    {
                        if (hitThing.Destroyed)
                        {
                            break;
                        }
                        var secDinfo = new DamageInfo(
                            cur.def,
                            cur.amount,
                            projectilePropsCE.armorPenetration, //Armor Penetration
                            ExactRotation.eulerAngles.y,
                            launcher,
                            null,
                            def
                            );
                        hitThing.TakeDamage(secDinfo).AssociateWithLog(logEntry);
                    }
                }
            }
            else
            {
                SoundDefOf.BulletImpact_Ground.PlayOneShot(new TargetInfo(base.Position, map, false));

                //Only display a dirt hit for projectiles with a dropshadow
                if (base.castShadow)
                {
                    MoteMaker.MakeStaticMote(ExactPosition, map, ThingDefOf.Mote_ShotHit_Dirt, 1f);
                }
            }
            base.Impact(hitThing);
        }
        /// <summary>
        ///     Takes into account the target being downed and the projectile having been fired while the target was downed, and
        ///     the target's bodySize
        /// </summary>
        private bool ImpactThroughBodySize(Thing thing, float height)
        {
            Pawn pawn = thing as Pawn;

            if (pawn != null)
            {
                PersonalShield shield = null;
                if (pawn.RaceProps.Humanlike)
                {
                    // check for shield user

                    List <Apparel> wornApparel = pawn.apparel.WornApparel;
                    for (int i = 0; i < wornApparel.Count; i++)
                    {
                        if (wornApparel[i] is PersonalShield)
                        {
                            shield = (PersonalShield)wornApparel[i];
                            break;
                        }
                    }
                }
                //Add suppression
                CompSuppressable compSuppressable = pawn.TryGetComp <CompSuppressable>();
                if (compSuppressable != null)
                {
                    if (shield == null || (shield != null && shield?.ShieldState == ShieldState.Resetting))
                    {
                        /*
                         * if (pawn.skills.GetSkill(SkillDefOf.Shooting).level >= 1)
                         * {
                         *  suppressionAmount = (def.projectile.damageAmountBase * (1f - ((pawn.skills.GetSkill(SkillDefOf.Shooting).level) / 100) * 3));
                         * }
                         * else suppressionAmount = def.projectile.damageAmountBase;
                         */
                        suppressionAmount = def.projectile.damageAmountBase;
                        ProjectilePropertiesCE propsCE = def.projectile as ProjectilePropertiesCE;
                        float penetrationAmount        = propsCE == null ? 0f : propsCE.armorPenetration;
                        suppressionAmount *= 1 - Mathf.Clamp(compSuppressable.parentArmor - penetrationAmount, 0, 1);
                        compSuppressable.AddSuppression(suppressionAmount, origin.ToIntVec3());
                    }
                }

                //Check horizontal distance
                Vector3 dest              = destination;
                Vector3 orig              = origin;
                Vector3 pawnPos           = pawn.DrawPos;
                float   closestDistToPawn = Math.Abs((dest.z - orig.z) * pawnPos.x - (dest.x - orig.x) * pawnPos.z +
                                                     dest.x * orig.z - dest.z * orig.x)
                                            /
                                            (float)
                                            Math.Sqrt((dest.z - orig.z) * (dest.z - orig.z) +
                                                      (dest.x - orig.x) * (dest.x - orig.x));
                if (closestDistToPawn <= CE_Utility.GetCollisionWidth(pawn))
                {
                    //Check vertical distance
                    float pawnHeight = CE_Utility.GetCollisionHeight(pawn);
                    if (height < pawnHeight)
                    {
                        Impact(thing);
                        return(true);
                    }
                }
            }
            if (thing.def.fillPercent > 0 || thing.def.Fillage == FillCategory.Full)
            {
                if (height < CE_Utility.GetCollisionHeight(thing) || thing.def.Fillage == FillCategory.Full)
                {
                    Impact(thing);
                    return(true);
                }
            }
            return(false);
        }
        private void ApplyDamagePartial(DamageInfo dinfo, Pawn pawn, ref LocalInjuryResult result)
        {
            BodyPartRecord exactPartFromDamageInfo = GetExactPartFromDamageInfo(dinfo, pawn);

            if (exactPartFromDamageInfo == null)
            {
                return;
            }

            // Only apply armor if we propagate damage to the outside or the body part itself is outside, secondary damage types should directly damage organs, bypassing armor
            bool involveArmor = !dinfo.InstantOldInjury &&
                                !result.deflected &&
                                (dinfo.Def.harmAllLayersUntilOutside || exactPartFromDamageInfo.depth == BodyPartDepth.Outside);
            int damageAmount = dinfo.Amount;

            if (involveArmor)
            {
                damageAmount = CE_Utility.GetAfterArmorDamage(pawn, dinfo.Amount, exactPartFromDamageInfo, dinfo, true, ref result.deflected);
            }
            if ((double)damageAmount < 0.001)
            {
                result.absorbed = true;
                return;
            }

            // Shot absorbed and converted into blunt
            DamageDef_CE damageDefCE = dinfo.Def as DamageDef_CE;

            if (damageDefCE != null &&
                damageDefCE.deflectable &&
                result.deflected &&
                dinfo.Def != CE_Utility.absorbDamageDef)
            {
                // Get outer parent of struck part
                BodyPartRecord currentPart = exactPartFromDamageInfo;
                while (currentPart != null && currentPart.parent != null && currentPart.depth != BodyPartDepth.Outside)
                {
                    currentPart = currentPart.parent;
                }
                DamageInfo dinfo2 = new DamageInfo(CE_Utility.absorbDamageDef, damageAmount, dinfo.Angle, dinfo.Instigator, currentPart, dinfo.WeaponGear);
                ApplyDamagePartial(dinfo2, pawn, ref result);
                return;
            }

            //Creating the Hediff
            HediffDef     hediffDefFromDamage = HealthUtility.GetHediffDefFromDamage(dinfo.Def, pawn, exactPartFromDamageInfo);
            Hediff_Injury hediff_Injury       = (Hediff_Injury)HediffMaker.MakeHediff(hediffDefFromDamage, pawn, null);

            hediff_Injury.Part   = exactPartFromDamageInfo;
            hediff_Injury.source = dinfo.WeaponGear;
            hediff_Injury.sourceBodyPartGroup = dinfo.WeaponBodyPartGroup;
            hediff_Injury.sourceHediffDef     = dinfo.WeaponLinkedHediff;
            hediff_Injury.Severity            = (float)damageAmount;
            if (dinfo.InstantOldInjury)
            {
                HediffComp_GetsOld hediffComp_GetsOld = hediff_Injury.TryGetComp <HediffComp_GetsOld>();
                if (hediffComp_GetsOld != null)
                {
                    hediffComp_GetsOld.IsOld = true;
                }
                else
                {
                    Log.Error(string.Concat(new object[]
                    {
                        "Tried to create instant old injury on Hediff without a GetsOld comp: ",
                        hediffDefFromDamage,
                        " on ",
                        pawn
                    }));
                }
            }
            result.wounded     = true;
            result.lastHitPart = hediff_Injury.Part;
            if (IsHeadshot(dinfo, hediff_Injury, pawn))
            {
                result.headshot = true;
            }
            if (dinfo.InstantOldInjury && (hediff_Injury.def.CompPropsFor(typeof(HediffComp_GetsOld)) == null || hediff_Injury.Part.def.oldInjuryBaseChance == 0f || hediff_Injury.Part.def.IsSolid(hediff_Injury.Part, pawn.health.hediffSet.hediffs) || pawn.health.hediffSet.PartOrAnyAncestorHasDirectlyAddedParts(hediff_Injury.Part)))
            {
                return;
            }
            FinalizeAndAddInjury(pawn, hediff_Injury, dinfo, ref result);
            CheckPropagateDamageToInnerSolidParts(dinfo, pawn, hediff_Injury, !dinfo.InstantOldInjury, damageAmount, ref result);
            CheckDuplicateDamageToOuterParts(dinfo, pawn, hediff_Injury, !dinfo.InstantOldInjury, damageAmount, ref result);
        }
Exemple #26
0
        // Added targetThing to parameters so we can calculate its height
        private bool CanHitCellFromCellIgnoringRange(Vector3 shotSource, IntVec3 targetLoc, Thing targetThing = null)
        {
            // Vanilla checks
            if (verbProps.mustCastOnOpenGround && (!targetLoc.Standable(caster.Map) || caster.Map.thingGrid.CellContains(targetLoc, ThingCategory.Pawn)))
            {
                return(false);
            }
            if (verbProps.requireLineOfSight)
            {
                // Calculate shot vector
                Vector3 targetPos;
                if (targetThing != null)
                {
                    Vector3 targDrawPos = targetThing.DrawPos;
                    targetPos = new Vector3(targDrawPos.x, new CollisionVertical(targetThing).Max, targDrawPos.z);
                    var targPawn = targetThing as Pawn;
                    if (targPawn != null)
                    {
                        targetPos += targPawn.Drawer.leaner.LeanOffset * 0.6f;
                    }
                }
                else
                {
                    targetPos = targetLoc.ToVector3Shifted();
                }
                Ray shotLine = new Ray(shotSource, (targetPos - shotSource));

                // Create validator to check for intersection with partial cover
                var aimMode = CompFireModes?.CurrentAimMode;

                Predicate <IntVec3> CanShootThroughCell = (IntVec3 cell) =>
                {
                    Thing cover = cell.GetFirstPawn(caster.Map) ?? cell.GetCover(caster.Map);

                    if (cover != null && cover != ShooterPawn && cover != caster && cover != targetThing && !cover.IsPlant() && !(cover is Pawn && cover.HostileTo(caster)))
                    {
                        // Skip this check entirely if we're doing suppressive fire and cell is adjacent to target
                        if ((VerbPropsCE.ignorePartialLoSBlocker || aimMode == AimMode.SuppressFire) && cover.def.Fillage != FillCategory.Full)
                        {
                            return(true);
                        }

                        Bounds bounds = CE_Utility.GetBoundsFor(cover);

                        // Simplified calculations for adjacent cover for gameplay purposes
                        if (cover.def.Fillage != FillCategory.Full && cover.AdjacentTo8WayOrInside(caster))
                        {
                            // Sanity check to prevent stuff behind us blocking LoS
                            var cellTargDist = cell.DistanceTo(targetLoc);
                            var shotTargDist = shotSource.ToIntVec3().DistanceTo(targetLoc);

                            if (shotTargDist > cellTargDist)
                            {
                                return(cover is Pawn || bounds.size.y < shotSource.y);
                            }
                        }

                        // Check for intersect
                        if (bounds.IntersectRay(shotLine))
                        {
                            if (Controller.settings.DebugDrawPartialLoSChecks)
                            {
                                caster.Map.debugDrawer.FlashCell(cell, 0, bounds.extents.y.ToString());
                            }
                            return(false);
                        }

                        if (Controller.settings.DebugDrawPartialLoSChecks)
                        {
                            caster.Map.debugDrawer.FlashCell(cell, 0.7f, bounds.extents.y.ToString());
                        }
                    }

                    return(true);
                };

                // Add validator to parameters
                foreach (IntVec3 curCell in SightUtility.GetCellsOnLine(shotSource, targetLoc.ToVector3(), caster.Map))
                {
                    if (Controller.settings.DebugDrawPartialLoSChecks)
                    {
                        caster.Map.debugDrawer.FlashCell(curCell, 0.4f);
                    }
                    if (curCell != shotSource.ToIntVec3() && curCell != targetLoc && !CanShootThroughCell(curCell))
                    {
                        return(false);
                    }
                }
            }
            return(true);
        }
Exemple #27
0
        public string GetTextReadout()
        {
            StringBuilder stringBuilder = new StringBuilder();

            if (visibilityShift > 0)
            {
                stringBuilder.AppendLine("   " + "CE_VisibilityError".Translate() + "\t" + GenText.ToStringByStyle(visibilityShift, ToStringStyle.FloatTwo) + " c");

                if (lightingShift > 0)
                {
                    stringBuilder.AppendLine("      " + "Darkness".Translate() + "\t" + AsPercent(lightingShift));
                }
                if (weatherShift > 0)
                {
                    stringBuilder.AppendLine("      " + "Weather".Translate() + "\t" + AsPercent(weatherShift));
                }
            }
            if (leadShift > 0)
            {
                stringBuilder.AppendLine("   " + "CE_LeadError".Translate() + "\t" + GenText.ToStringByStyle(leadShift, ToStringStyle.FloatTwo) + " c");
            }
            if (distShift > 0)
            {
                stringBuilder.AppendLine("   " + "CE_RangeError".Translate() + "\t" + GenText.ToStringByStyle(distShift, ToStringStyle.FloatTwo) + " c");
            }
            if (swayDegrees > 0)
            {
                stringBuilder.AppendLine("   " + "CE_Sway".Translate() + "\t\t" + GenText.ToStringByStyle(swayDegrees, ToStringStyle.FloatTwo) + "°");
            }
            if (spreadDegrees > 0)
            {
                stringBuilder.AppendLine("   " + "CE_Spread".Translate() + "\t\t" + GenText.ToStringByStyle(spreadDegrees, ToStringStyle.FloatTwo) + "°");
            }
            // Don't display cover and target size if our weapon has a CEP
            if (circularMissRadius > 0)
            {
                stringBuilder.AppendLine("   " + "CE_MissRadius".Translate() + "\t" + GenText.ToStringByStyle(circularMissRadius, ToStringStyle.FloatTwo) + " c");
                if (indirectFireShift > 0)
                {
                    stringBuilder.AppendLine("   " + "CE_IndirectFire".Translate() + "\t" + GenText.ToStringByStyle(indirectFireShift, ToStringStyle.FloatTwo) + " c");
                }
            }
            else
            {
                if (cover != null)
                {
                    stringBuilder.AppendLine("   " + "CE_CoverHeight".Translate() + "\t" + GenText.ToStringByStyle(CE_Utility.GetCollisionHeight(cover), ToStringStyle.FloatTwo) + " c");
                }
                if (target.Thing != null)
                {
                    stringBuilder.AppendLine("   " + "CE_TargetHeight".Translate() + "\t" + GenText.ToStringByStyle(CE_Utility.GetCollisionHeight(target.Thing), ToStringStyle.FloatTwo) + " c");
                    stringBuilder.AppendLine("   " + "CE_TargetWidth".Translate() + "\t" + GenText.ToStringByStyle(CE_Utility.GetCollisionWidth(target.Thing) * 2, ToStringStyle.FloatTwo) + " c");
                }
            }
            return(stringBuilder.ToString());
        }