public virtual ShiftVecReport ShiftVecReportFor(LocalTargetInfo target) { IntVec3 targetCell = target.Cell; ShiftVecReport report = new ShiftVecReport(); report.target = target; report.aimingAccuracy = this.AimingAccuracy; report.sightsEfficiency = this.SightsEfficiency; report.shotDist = (targetCell - this.caster.Position).LengthHorizontal; report.maxRange = verbProps.range; report.lightingShift = 1 - caster.Map.glowGrid.GameGlowAt(targetCell); if (!this.caster.Position.Roofed(caster.Map) || !targetCell.Roofed(caster.Map)) //Change to more accurate algorithm? { report.weatherShift = 1 - caster.Map.weatherManager.CurWeatherAccuracyMultiplier; } report.shotSpeed = this.ShotSpeed; report.swayDegrees = this.SwayAmplitude; var spreadmult = this.projectilePropsCE != null ? this.projectilePropsCE.spreadMult : 0f; report.spreadDegrees = this.EquipmentSource.GetStatValue(StatDef.Named("ShotSpread")) * spreadmult; Thing cover; float smokeDensity; this.GetHighestCoverAndSmokeForTarget(target, out cover, out smokeDensity); report.cover = cover; report.smokeDensity = smokeDensity; return(report); }
public override ShiftVecReport ShiftVecReportFor(LocalTargetInfo target) { ShiftVecReport report = base.ShiftVecReportFor(target); report.shotDist = Vector3.Distance(target.CenterVector3, caster.TrueCenter()) * DISTFACTOR_FACTOR; return(report); }
/// <summary> /// Fires a projectile using the new aiming system /// </summary> /// <returns>True for successful shot, false otherwise</returns> protected override bool TryCastShot() { ShootLine shootLine; if (!TryFindCEShootLineFromTo(caster.Position, currentTarget, out shootLine)) { return(false); } if (projectilePropsCE.pelletCount < 1) { Log.Error(ownerEquipment.LabelCap + " tried firing with pelletCount less than 1."); return(false); } ShiftVecReport report = ShiftVecReportFor(currentTarget); bool pelletMechanicsOnly = false; for (int i = 0; i < projectilePropsCE.pelletCount; i++) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(ProjectileDef, null); GenSpawn.Spawn(projectile, shootLine.Source, caster.Map); ShiftTarget(report, pelletMechanicsOnly); //New aiming algorithm projectile.canTargetSelf = false; projectile.minCollisionSqr = (sourceLoc - currentTarget.Cell.ToIntVec2.ToVector2Shifted()).sqrMagnitude; projectile.Launch(caster, sourceLoc, shotAngle, shotRotation, ShotHeight, ShotSpeed, ownerEquipment); pelletMechanicsOnly = true; } pelletMechanicsOnly = false; this.numShotsFired++; return(true); }
public override bool TryCastShot() { ArtilleryMarker marker = ThingMaker.MakeThing(ThingDef.Named(ArtilleryMarker.MarkerDef)) as ArtilleryMarker; ShiftVecReport report = ShiftVecReportFor(currentTarget); marker.sightsEfficiency = report.sightsEfficiency; marker.aimingAccuracy = report.aimingAccuracy; marker.lightingShift = report.lightingShift; marker.weatherShift = report.weatherShift; GenSpawn.Spawn(marker, this.currentTarget.Cell, caster.Map); // Check for something to attach marker to if (this.currentTarget.HasThing) { CompAttachBase comp = this.currentTarget.Thing.TryGetComp <CompAttachBase>(); if (comp != null) { marker.AttachTo(this.currentTarget.Thing); } } // Show we learned something PlayerKnowledgeDatabase.KnowledgeDemonstrated(CE_ConceptDefOf.CE_Spotting, KnowledgeAmount.SmallInteraction); return(true); }
/// <summary> /// Fires a projectile using the new aiming system /// </summary> /// <returns>True for successful shot, false otherwise</returns> protected override bool TryCastShot() { if (!TryFindCEShootLineFromTo(caster.Position, currentTarget, out var shootLine)) { return(false); } if (projectilePropsCE.pelletCount < 1) { Log.Error(EquipmentSource.LabelCap + " tried firing with pelletCount less than 1."); return(false); } ShiftVecReport report = ShiftVecReportFor(currentTarget); bool pelletMechanicsOnly = false; for (int i = 0; i < projectilePropsCE.pelletCount; i++) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(Projectile, null); GenSpawn.Spawn(projectile, shootLine.Source, caster.Map); ShiftTarget(report, pelletMechanicsOnly); //New aiming algorithm projectile.canTargetSelf = false; var targDist = (sourceLoc - currentTarget.Cell.ToIntVec2.ToVector2Shifted()).magnitude; if (targDist <= 2) { targDist *= 2; // Double to account for divide by 4 in ProjectileCE minimum collision distance calculations } projectile.minCollisionSqr = Mathf.Pow(targDist, 2); projectile.intendedTarget = currentTarget.Thing; projectile.mount = caster.Position.GetThingList(caster.Map).FirstOrDefault(t => t is Pawn && t != caster); projectile.AccuracyFactor = report.accuracyFactor * report.swayDegrees * ((numShotsFired + 1) * 0.75f); projectile.Launch( Shooter, //Shooter instead of caster to give turret operators' records the damage/kills obtained sourceLoc, shotAngle, shotRotation, ShotHeight, ShotSpeed, EquipmentSource ); pelletMechanicsOnly = true; } /// Log.Message("Fired from "+caster.ThingID+" at "+ShotHeight); /// pelletMechanicsOnly = false; numShotsFired++; if (CompAmmo != null && !CompAmmo.CanBeFiredNow) { CompAmmo?.TryStartReload(); } if (CompReloadable != null) { CompReloadable.UsedOnce(); } return(true); }
// Copy-constructor public ShiftVecReport(ShiftVecReport report) { target = report.target; aimEfficiency = report.aimEfficiency; aimingAccuracy = report.aimingAccuracy; circularMissRadius = report.circularMissRadius; indirectFireShift = report.indirectFireShift; lightingShift = report.lightingShift; shotSpeed = report.shotSpeed; shotDist = report.shotDist; isAiming = report.isAiming; swayDegrees = report.swayDegrees; spreadDegrees = report.spreadDegrees; cover = report.cover; }
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); }
/// <summary> /// Fires a projectile using the new aiming system /// </summary> /// <returns>True for successful shot, false otherwise</returns> protected override bool TryCastShot() { if (!TryFindCEShootLineFromTo(caster.Position, currentTarget, out var shootLine)) { return(false); } if (projectilePropsCE.pelletCount < 1) { Log.Error(EquipmentSource.LabelCap + " tried firing with pelletCount less than 1."); return(false); } ShiftVecReport report = ShiftVecReportFor(currentTarget); bool pelletMechanicsOnly = false; for (int i = 0; i < projectilePropsCE.pelletCount; i++) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(Projectile, null); GenSpawn.Spawn(projectile, shootLine.Source, caster.Map); ShiftTarget(report, pelletMechanicsOnly); //New aiming algorithm projectile.canTargetSelf = false; projectile.minCollisionSqr = (sourceLoc - currentTarget.Cell.ToIntVec2.ToVector2Shifted()).sqrMagnitude; projectile.intendedTarget = currentTarget.Thing; projectile.mount = caster.Position.GetThingList(caster.Map).FirstOrDefault(t => t is Pawn && t != caster); projectile.AccuracyFactor = report.accuracyFactor * report.swayDegrees * ((numShotsFired + 1) * 0.75f); projectile.Launch( Shooter, //Shooter instead of caster to give turret operators' records the damage/kills obtained sourceLoc, shotAngle, shotRotation, ShotHeight, ShotSpeed, EquipmentSource ); pelletMechanicsOnly = true; } pelletMechanicsOnly = false; numShotsFired++; return(true); }
/// <summary> /// Fires a projectile using the new aiming system /// </summary> /// <returns>True for successful shot, false otherwise</returns> protected override bool TryCastShot() { ShootLine shootLine; if (!base.TryFindShootLineFromTo(this.caster.Position, this.currentTarget, out shootLine)) { return(false); } if (this.projectilePropsCE.pelletCount < 1) { Log.Error(this.ownerEquipment.LabelCap + " tried firing with pelletCount less than 1."); return(false); } for (int i = 0; i < this.projectilePropsCE.pelletCount; i++) { Vector3 casterExactPosition = this.caster.DrawPos; ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(projectileDef, null); GenSpawn.Spawn(projectile, shootLine.Source, caster.Map); float lengthHorizontalSquared = (this.currentTarget.Cell - this.caster.Position).LengthHorizontalSquared; //New aiming algorithm projectile.canFreeIntercept = true; ShiftVecReport report = ShiftVecReportFor(this.currentTarget); Vector3 targetVec3 = this.ShiftTarget(report); projectile.shotAngle = this.shotAngle; projectile.shotHeight = this.shotHeight; projectile.shotSpeed = this.shotSpeed; if (this.currentTarget.Thing != null) { projectile.Launch(this.caster, casterExactPosition, new LocalTargetInfo(this.currentTarget.Thing), targetVec3, this.ownerEquipment); } else { projectile.Launch(this.caster, casterExactPosition, new LocalTargetInfo(shootLine.Dest), targetVec3, this.ownerEquipment); } } this.numShotsFired++; return(true); }
protected override bool TryCastShot() { ArtilleryMarker marker = ThingMaker.MakeThing(ThingDef.Named(ArtilleryMarker.MarkerDef)) as ArtilleryMarker; ShiftVecReport report = ShiftVecReportFor(currentTarget); marker.aimEfficiency = report.aimEfficiency; marker.aimingAccuracy = report.aimingAccuracy; marker.lightingShift = report.lightingShift; marker.weatherShift = report.weatherShift; GenSpawn.Spawn(marker, this.currentTarget.Cell, caster.Map); // Check for something to attach marker to if (this.currentTarget.HasThing) { CompAttachBase comp = this.currentTarget.Thing.TryGetComp <CompAttachBase>(); if (comp != null) { marker.AttachTo(this.currentTarget.Thing); } } return(true); }
/// <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; }
/// <summary> /// Fires a projectile using the new aiming system /// </summary> /// <returns>True for successful shot, false otherwise</returns> protected override bool TryCastShot() { ShootLine shootLine; if (!TryFindCEShootLineFromTo(caster.Position, currentTarget, out shootLine)) { return(false); } Pawn p = CasterPawn != null ? CasterPawn : caster as Pawn; if (caster != null) { // Log.Message("caster: " + caster.ToString() + " pos: " + caster.Position.ToString()); } if (CasterPawn != null) { // Log.Message("casterPawn: " + CasterPawn.ToString() + " pos: " + CasterPawn.Position.ToString()); } // Log.Message("currentTarget: " + currentTarget.ToString() + " pos: " + currentTarget.Cell.ToString()); if (p != null && !p.IsColonistPlayerControlled && p.equipment != null && p.equipment.Primary != null) { if (numShotsFired == 0) { CompFireModes compFireModes = p.equipment.Primary.TryGetComp <CompFireModes>(); if (compFireModes != null) { float lengthToTarget = Mathf.Abs((currentTarget.Cell - caster.Position).LengthHorizontal); float rangemultiplier = this.VerbPropsCE.range / 60; if (lengthToTarget <= (30 * rangemultiplier)) { if (compFireModes.availableAimModes.Contains(AimMode.Snapshot) && compFireModes.currentAimModeInt != AimMode.Snapshot) { compFireModes.currentAimModeInt = AimMode.Snapshot; // Log.Message("selected aimmode1: " + compFireModes.currentAimModeInt); } } else { if (compFireModes.availableAimModes.Contains(AimMode.AimedShot) && compFireModes.currentAimModeInt != AimMode.AimedShot) { compFireModes.currentAimModeInt = AimMode.AimedShot; // Log.Message("selected aimmode2: " + compFireModes.currentAimModeInt); } } if (compFireModes.availableFireModes.Count > 1) { if (lengthToTarget <= (50 * rangemultiplier)) { if (compFireModes.availableFireModes.Contains(FireMode.AutoFire) && compFireModes.currentFireModeInt != FireMode.AutoFire) { compFireModes.currentFireModeInt = FireMode.AutoFire; // Log.Message("selected firemode1: " + compFireModes.currentFireModeInt.ToString()); } } else { if (compFireModes.availableFireModes.Contains(FireMode.SingleFire) && compFireModes.currentFireModeInt != FireMode.SingleFire) { compFireModes.currentFireModeInt = FireMode.SingleFire; // Log.Message("selected firemode2: " + compFireModes.currentFireModeInt.ToString()); } } } } } } if (p != null) { if ((Mathf.Abs(cachedShotRotation - shotRotation)) >= 15 && numShotsFired == 0) { cachedShotRotation = shotRotation; p.Drawer.Notify_WarmingCastAlongLine(shootLine, caster.Position); } } if (projectilePropsCE.pelletCount < 1) { Log.Error(EquipmentSource.LabelCap + " tried firing with pelletCount less than 1."); return(false); } ShiftVecReport report = ShiftVecReportFor(currentTarget); bool pelletMechanicsOnly = false; for (int i = 0; i < projectilePropsCE.pelletCount; i++) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(Projectile, null); GenSpawn.Spawn(projectile, shootLine.Source, caster.Map); ShiftTarget(report, pelletMechanicsOnly); //New aiming algorithm projectile.canTargetSelf = false; projectile.minCollisionSqr = (sourceLoc - currentTarget.Cell.ToIntVec2.ToVector2Shifted()).sqrMagnitude; projectile.intendedTarget = currentTarget.Thing; projectile.Launch( Shooter, //Shooter instead of caster to give turret operators' records the damage/kills obtained sourceLoc, shotAngle, shotRotation, ShotHeight, ShotSpeed, EquipmentSource ); pelletMechanicsOnly = true; // Log.Message("proj: " + projectile.ToString() + " shootlineSource: " + shootLine.Source.ToString() + " caster: " + caster.ToString() + " currentTarget.Thing: " + currentTarget.Thing.ToString() + " report: " + report.ToString()); // Log.Message("minCollisionSqr: " + projectile.minCollisionSqr.ToString() + " intendedTarget : " + projectile.intendedTarget.ToString() + " Shooter: " + Shooter.ToString() // + " sourceLoc: " + sourceLoc.ToString() + " shotAngle: " + shotAngle.ToString() + " shotRotation: " + shotRotation.ToString() + " ShotHeight: " + ShotHeight.ToString() // + " ShotHeight: " + ShotHeight.ToString() + "ShotSpeed: " + ShotSpeed.ToString() + " EquipmentSource: " + EquipmentSource.ToString()); } pelletMechanicsOnly = false; this.numShotsFired++; return(true); }
/// <summary> /// Fires a projectile using the new aiming system /// </summary> /// <returns>True for successful shot, false otherwise</returns> public override bool TryCastShot() { if (!TryFindCEShootLineFromTo(caster.Position, currentTarget, out var shootLine)) { return(false); } if (projectilePropsCE.pelletCount < 1) { Log.Error(EquipmentSource.LabelCap + " tried firing with pelletCount less than 1."); return(false); } bool instant = false; float spreadDegrees = 0; float aperatureSize = 0; if (Projectile.projectile is ProjectilePropertiesCE pprop) { instant = pprop.isInstant; spreadDegrees = (EquipmentSource?.GetStatValue(StatDef.Named("ShotSpread")) ?? 0) * pprop.spreadMult; aperatureSize = 0.03f; } ShiftVecReport report = ShiftVecReportFor(currentTarget); bool pelletMechanicsOnly = false; for (int i = 0; i < projectilePropsCE.pelletCount; i++) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(Projectile, null); GenSpawn.Spawn(projectile, shootLine.Source, caster.Map); ShiftTarget(report, pelletMechanicsOnly, instant); //New aiming algorithm projectile.canTargetSelf = false; var targetDistance = (sourceLoc - currentTarget.Cell.ToIntVec2.ToVector2Shifted()).magnitude; projectile.minCollisionDistance = GetMinCollisionDistance(targetDistance); projectile.intendedTarget = currentTarget; projectile.mount = caster.Position.GetThingList(caster.Map).FirstOrDefault(t => t is Pawn && t != caster); projectile.AccuracyFactor = report.accuracyFactor * report.swayDegrees * ((numShotsFired + 1) * 0.75f); if (instant) { var shotHeight = ShotHeight; float tsa = AdjustShotHeight(caster, currentTarget, ref shotHeight); projectile.RayCast( Shooter, verbProps, sourceLoc, shotAngle + tsa, shotRotation, shotHeight, ShotSpeed, spreadDegrees, aperatureSize, EquipmentSource); } else { projectile.Launch( Shooter, //Shooter instead of caster to give turret operators' records the damage/kills obtained sourceLoc, shotAngle, shotRotation, ShotHeight, ShotSpeed, EquipmentSource); } pelletMechanicsOnly = true; } /* * Notify the lighting tracker that shots fired with muzzle flash value of VerbPropsCE.muzzleFlashScale */ LightingTracker.Notify_ShotsFiredAt(caster.Position, intensity: VerbPropsCE.muzzleFlashScale); pelletMechanicsOnly = false; numShotsFired++; if (CompAmmo != null && !CompAmmo.CanBeFiredNow) { CompAmmo?.TryStartReload(); } if (CompReloadable != null) { CompReloadable.UsedOnce(); } 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); }