protected override void Impact(Thing hitThing) { Map map = base.Map; base.Impact(hitThing); if (hitThing != null) { int damageAmountBase = def.projectile.damageAmountBase; ThingDef equipmentDef = this.equipmentDef; DamageDefExtensionCE damDefCE = def.projectile.damageDef.GetModExtension <DamageDefExtensionCE>() ?? new DamageDefExtensionCE(); DamageInfo dinfo = new DamageInfo( def.projectile.damageDef, damageAmountBase, ExactRotation.eulerAngles.y, launcher, null, def); // Set impact height BodyPartDepth partDepth = damDefCE != null && damDefCE.harmOnlyOutsideLayers ? BodyPartDepth.Outside : BodyPartDepth.Undefined; BodyPartHeight partHeight = new CollisionVertical(hitThing).GetCollisionBodyHeight(Height); dinfo.SetBodyRegion(partHeight, partDepth); if (damDefCE != null && damDefCE.harmOnlyOutsideLayers) { dinfo.SetBodyRegion(BodyPartHeight.Undefined, BodyPartDepth.Outside); } // Apply primary damage hitThing.TakeDamage(dinfo); // Apply secondary to non-pawns (pawn secondary damage is handled in the damage worker) var projectilePropsCE = def.projectile as ProjectilePropertiesCE; 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, ExactRotation.eulerAngles.y, launcher, null, def); hitThing.TakeDamage(secDinfo); } } } else { SoundDefOf.BulletImpactGround.PlayOneShot(new TargetInfo(base.Position, map, false)); MoteMaker.MakeStaticMote(ExactPosition, map, ThingDefOf.Mote_ShotHit_Dirt, 1f); } }
/// <summary> /// Selects a random BodyPartHeight out of the ones our CasterPawn can hit, depending on our body size vs the target's. So a rabbit can hit top height of another rabbit, but not of a human. /// </summary> /// <param name="target"></param> /// <returns></returns> private BodyPartHeight GetBodyPartHeightFor(LocalTargetInfo target) { Pawn pawn = target.Thing as Pawn; if (pawn == null || CasterPawn == null) { return(BodyPartHeight.Undefined); } var casterReach = new CollisionVertical(CasterPawn).Max * 1.2f; var targetHeight = new CollisionVertical(pawn); BodyPartHeight maxHeight = targetHeight.GetRandWeightedBodyHeightBelow(casterReach); BodyPartHeight height = (BodyPartHeight)Rand.RangeInclusive(1, (int)maxHeight); return(height); }
public static Bounds GetBoundsFor(Thing thing) { if (thing == null) { return(new Bounds()); } var height = new CollisionVertical(thing); var width = GetCollisionWidth(thing); var thingPos = thing.DrawPos; thingPos.y = height.Max - height.HeightRange.Span / 2; Bounds bounds = new Bounds(thingPos, new Vector3(width, height.HeightRange.Span, width)); return(bounds); }
protected override void Impact(Thing hitThing) { Map map = base.Map; base.Impact(hitThing); if (hitThing != null) { int damageAmountBase = def.projectile.damageAmountBase; ThingDef equipmentDef = this.equipmentDef; DamageDefExtensionCE damDefCE = def.projectile.damageDef.GetModExtension <DamageDefExtensionCE>() ?? new DamageDefExtensionCE(); DamageInfo dinfo = new DamageInfo( def.projectile.damageDef, damageAmountBase, ExactRotation.eulerAngles.y, launcher, null, def); // Set impact height BodyPartDepth partDepth = damDefCE != null && damDefCE.harmOnlyOutsideLayers ? BodyPartDepth.Outside : BodyPartDepth.Undefined; BodyPartHeight partHeight = new CollisionVertical(hitThing).GetCollisionBodyHeight(Height); dinfo.SetBodyRegion(partHeight, partDepth); if (damDefCE != null && damDefCE.harmOnlyOutsideLayers) { dinfo.SetBodyRegion(BodyPartHeight.Undefined, BodyPartDepth.Outside); } // Apply primary damage hitThing.TakeDamage(dinfo); } else { SoundDefOf.BulletImpactGround.PlayOneShot(new TargetInfo(base.Position, map, false)); MoteMaker.MakeStaticMote(ExactPosition, map, ThingDefOf.Mote_ShotHit_Dirt, 1f); } }
/// <summary> /// Checks for cover along the flight path of the bullet, doesn't check for walls or trees, only intended for cover with partial fillPercent /// </summary> /// <param name="target">The target of which to find cover of</param> /// <param name="cover">Output parameter, filled with the highest cover object found</param> /// <returns>True if cover was found, false otherwise</returns> private bool GetHighestCoverAndSmokeForTarget(LocalTargetInfo target, out Thing cover, out float smokeDensity) { Map map = caster.Map; Thing targetThing = target.Thing; Thing highestCover = null; float highestCoverHeight = 0f; smokeDensity = 0; // Iterate through all cells on line of sight and check for cover and smoke var cells = GenSight.PointsOnLineOfSight(target.Cell, caster.Position).ToArray(); if (cells.Length < 3) { cover = null; return(false); } for (int i = 0; i <= cells.Length / 2; i++) { var cell = cells[i]; if (cell.AdjacentTo8Way(caster.Position)) { continue; } // Check for smoke var gas = cell.GetGas(map); if (gas != null) { smokeDensity += gas.def.gas.accuracyPenalty; } // Check for cover in the second half of LoS if (i <= cells.Length / 2) { Pawn pawn = cell.GetFirstPawn(map); Thing newCover = pawn == null?cell.GetCover(map) : pawn; float newCoverHeight = new CollisionVertical(newCover).Max; // Cover check, if cell has cover compare collision height and get the highest piece of cover, ignore if cover is the target (e.g. solar panels, crashed ship, etc) if (newCover != null && (targetThing == null || !newCover.Equals(targetThing)) && (highestCover == null || highestCoverHeight < newCoverHeight) && newCover.def.Fillage == FillCategory.Partial && !newCover.IsPlant()) { highestCover = newCover; highestCoverHeight = newCoverHeight; if (Controller.settings.DebugDrawTargetCoverChecks) { map.debugDrawer.FlashCell(cell, highestCoverHeight, highestCoverHeight.ToString()); } } } } cover = highestCover; //Report success if found cover return(cover != null); }
/// <summary> /// Shifts the original target position in accordance with target leading, range estimation and weather/lighting effects /// </summary> protected virtual void ShiftTarget(ShiftVecReport report, bool calculateMechanicalOnly = false) { if (!calculateMechanicalOnly) { Vector3 u = caster.TrueCenter(); sourceLoc.Set(u.x, u.z); if (numShotsFired == 0) { // On first shot of burst do a range estimate estimatedTargDist = report.GetRandDist(); } Vector3 v = report.target.Thing?.TrueCenter() ?? report.target.Cell.ToVector3Shifted(); //report.targetPawn != null ? report.targetPawn.DrawPos + report.targetPawn.Drawer.leaner.LeanOffset * 0.5f : report.target.Cell.ToVector3Shifted(); if (report.targetPawn != null) { v += report.targetPawn.Drawer.leaner.LeanOffset * 0.5f; } newTargetLoc.Set(v.x, v.z); // ----------------------------------- STEP 1: Actual location + Shift for visibility //FIXME : GetRandCircularVec may be causing recoil to be unnoticeable - each next shot in the burst has a new random circular vector around the target. newTargetLoc += report.GetRandCircularVec(); // ----------------------------------- STEP 2: Estimated shot to hit location newTargetLoc = sourceLoc + (newTargetLoc - sourceLoc).normalized * estimatedTargDist; // Lead a moving target newTargetLoc += report.GetRandLeadVec(); // ----------------------------------- STEP 3: Recoil, Skewing, Skill checks, Cover calculations rotationDegrees = 0f; angleRadians = 0f; GetSwayVec(ref rotationDegrees, ref angleRadians); GetRecoilVec(ref rotationDegrees, ref angleRadians); // Height difference calculations for ShotAngle float targetHeight = 0f; var coverRange = new CollisionVertical(report.cover).HeightRange; //Get " " cover, assume it is the edifice // Projectiles with flyOverhead target the surface in front of the target if (Projectile.projectile.flyOverhead) { targetHeight = coverRange.max; } else { var victimVert = new CollisionVertical(currentTarget.Thing); var targetRange = victimVert.HeightRange; //Get lower and upper heights of the target /*if (currentTarget.Thing is Building && CompFireModes?.CurrentAimMode == AimMode.SuppressFire) * { * targetRange.min = targetRange.max; * targetRange.max = targetRange.min + 1f; * }*/ if (targetRange.min < coverRange.max) //Some part of the target is hidden behind some cover { // - It is possible for targetRange.max < coverRange.max, technically, in which case the shooter will never hit until the cover is gone. // - This should be checked for in LoS -NIA targetRange.min = coverRange.max; // Target fully hidden, shift aim upwards if we're doing suppressive fire if (targetRange.max <= coverRange.max && CompFireModes?.CurrentAimMode == AimMode.SuppressFire) { targetRange.max = coverRange.max * 2; } } else if (currentTarget.Thing is Pawn) { // Aim for center of mass on an exposed target targetRange.min = victimVert.BottomHeight; targetRange.max = victimVert.MiddleHeight; } targetHeight = VerbPropsCE.ignorePartialLoSBlocker ? 0 : targetRange.Average; } angleRadians += ProjectileCE.GetShotAngle(ShotSpeed, (newTargetLoc - sourceLoc).magnitude, targetHeight - ShotHeight, Projectile.projectile.flyOverhead, projectilePropsCE.Gravity); } // ----------------------------------- STEP 4: Mechanical variation // Get shotvariation, in angle Vector2 RADIANS. Vector2 spreadVec = report.GetRandSpreadVec(); // ----------------------------------- STEP 5: Finalization var w = (newTargetLoc - sourceLoc); shotRotation = (-90 + Mathf.Rad2Deg * Mathf.Atan2(w.y, w.x) + rotationDegrees + spreadVec.x) % 360; shotAngle = angleRadians + spreadVec.y * Mathf.Deg2Rad; }
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) { int damageAmountBase = def.projectile.damageAmountBase; DamageDefExtensionCE damDefCE = def.projectile.damageDef.GetModExtension <DamageDefExtensionCE>() ?? new DamageDefExtensionCE(); DamageInfo dinfo = new DamageInfo( def.projectile.damageDef, damageAmountBase, 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).InsertIntoLog(logEntry); // Apply secondary to non-pawns (pawn secondary damage is handled in the damage worker) var projectilePropsCE = def.projectile as ProjectilePropertiesCE; 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, ExactRotation.eulerAngles.y, launcher, null, def); hitThing.TakeDamage(secDinfo).InsertIntoLog(logEntry); } } } else { SoundDefOf.BulletImpactGround.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); }
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; }
public override void Impact(Thing hitThing) { bool cookOff = (launcher is AmmoThing); Map map = base.Map; LogEntry_DamageResult logEntry = null; if (!cookOff && (logMisses || 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 float damageAmountBase = DamageAmount; var projectilePropsCE = (ProjectilePropertiesCE)def.projectile; var isSharpDmg = def.projectile.damageDef.armorCategory == DamageArmorCategoryDefOf.Sharp; var penetration = PenetrationAmount; var damDefCE = def.projectile.damageDef.GetModExtension <DamageDefExtensionCE>() ?? new DamageDefExtensionCE(); var dinfo = new DamageInfo( def.projectile.damageDef, damageAmountBase, penetration, //Armor Penetration ExactRotation.eulerAngles.y, launcher, null, def); // Set impact height BodyPartDepth partDepth = 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.harmOnlyOutsideLayers) { dinfo.SetBodyRegion(BodyPartHeight.Undefined, BodyPartDepth.Outside); } //The following code excludes turrets etcetera from having cook off projectile impacts recorded in their combat log. //If it is necessary to add cook off to turret logs, a new BattleLogEntry_ must be created, because BattleLogEntry_DamageTaken, //which is the only method capable of handling cookoff and only using pawns, can not take !(hitThing is Pawn). if (cookOff && hitThing is Pawn hitPawn) { logEntry = new BattleLogEntry_DamageTaken( hitPawn, CookOff ); Find.BattleLog.Add(logEntry); } try { // Apply primary damage hitThing.TakeDamage(dinfo).AssociateWithLog(logEntry); // Apply secondary to non-pawns (pawn secondary damage is handled in the damage worker) // The !(hitThing is Pawn) already excludes non-pawn cookoff projectiles from being logged, as logEntry == null if (!(hitThing is Pawn) && projectilePropsCE != null && !projectilePropsCE.secondaryDamage.NullOrEmpty()) { foreach (SecondaryDamage cur in projectilePropsCE.secondaryDamage) { if (hitThing.Destroyed || !Rand.Chance(cur.chance)) { break; } var secDinfo = cur.GetDinfo(dinfo); hitThing.TakeDamage(secDinfo).AssociateWithLog(logEntry); } } } catch (Exception e) { Log.Error("CombatExtended :: BulletCE impacting thing " + hitThing.LabelCap + " of def " + hitThing.def.LabelCap + " added by mod " + hitThing.def.modContentPack.Name + ". See following stacktrace for information."); throw e; } finally { base.Impact(hitThing); } } else { SoundDefOf.BulletImpact_Ground.PlayOneShot(new TargetInfo(base.Position, map, false)); //Only display a dirt/water hit for projectiles with a dropshadow if (base.castShadow) { FleckMaker.Static(this.ExactPosition, map, FleckDefOf.ShotHit_Dirt, 1f); if (base.Position.GetTerrain(map).takeSplashes) { FleckMaker.WaterSplash(this.ExactPosition, map, Mathf.Sqrt(def.projectile.GetDamageAmount(this.launcher)) * 1f, 4f); } } base.Impact(null); } NotifyImpact(hitThing, map, Position); }
protected override void Impact(Thing hitThing) { bool cookOff = (launcher is AmmoThing); Map map = base.Map; LogEntry_DamageResult logEntry = null; if (logMisses || (!logMisses && hitThing != null && (hitThing is Pawn || hitThing is Building_Turret) )) { if (!cookOff) { LogImpact(hitThing, out logEntry); } } if (hitThing != null) { // launcher being the pawn equipping the weapon, not the weapon itself int damageAmountBase = def.projectile.GetDamageAmount(1); DamageDefExtensionCE damDefCE = def.projectile.damageDef.GetModExtension <DamageDefExtensionCE>() ?? new DamageDefExtensionCE(); var projectilePropsCE = (ProjectilePropertiesCE)def.projectile; DamageInfo dinfo = new DamageInfo( def.projectile.damageDef, damageAmountBase, projectilePropsCE.GetArmorPenetration(1), //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); } //The following code excludes turrets etcetera from having cook off projectile impacts recorded in their combat log. //If it is necessary to add cook off to turret logs, a new BattleLogEntry_ must be created, because BattleLogEntry_DamageTaken, //which is the only method capable of handling cookoff and only using pawns, can not take !(hitThing is Pawn). if (cookOff && hitThing is Pawn) { logEntry = new BattleLogEntry_DamageTaken( (Pawn)hitThing, DefDatabase <RulePackDef> .GetNamed("DamageEvent_CookOff")); Find.BattleLog.Add(logEntry); } // Apply primary damage hitThing.TakeDamage(dinfo).AssociateWithLog(logEntry); // Apply secondary to non-pawns (pawn secondary damage is handled in the damage worker) // The !(hitThing is Pawn) already excludes non-pawn cookoff projectiles from being logged, as logEntry == null 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.GetArmorPenetration(1), //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/water hit for projectiles with a dropshadow if (base.castShadow) { MoteMaker.MakeStaticMote(this.ExactPosition, map, ThingDefOf.Mote_ShotHit_Dirt, 1f); if (base.Position.GetTerrain(map).takeSplashes) { MoteMaker.MakeWaterSplash(this.ExactPosition, map, Mathf.Sqrt(def.projectile.GetDamageAmount(this.launcher)) * 1f, 4f); } } } base.Impact(hitThing); }