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); }
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); }
/// <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); }
/// <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); }
/// <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); }
// 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); }
// 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> /// 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); }
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; } }
// 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); }