private bool TryLaunchCookOffProjectile() { if (AmmoDef.cookOffProjectile == null) { return(false); } ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(AmmoDef.cookOffProjectile); GenSpawn.Spawn(projectile, Position, Map); // Launch in random direction projectile.canTargetSelf = true; projectile.minCollisionSqr = 0f; projectile.Launch(this, new Vector2(DrawPos.x, DrawPos.z), UnityEngine.Random.Range(0, Mathf.PI / 2f), UnityEngine.Random.Range(0, 360), 0.1f, AmmoDef.cookOffProjectile.projectile.speed * AmmoDef.cookOffSpeed); if (AmmoDef.cookOffFlashScale > 0.01) { MoteMaker.MakeStaticMote(Position, Map, ThingDefOf.Mote_ShotFlash, AmmoDef.cookOffFlashScale); } if (AmmoDef.cookOffSound != null) { AmmoDef.cookOffSound.PlayOneShot(new TargetInfo(Position, Map)); } if (AmmoDef.cookOffTailSound != null) { AmmoDef.cookOffTailSound.PlayOneShotOnCamera(); } 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 (!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); }
/// <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); }
/// <summary> /// Produces a secondary explosion on impact using the explosion values from the projectile's projectile def. Requires the projectile's launcher to be passed on due to protection level, /// only works when parent can be cast as ProjectileCE. Intended use is for HEAT and similar weapons that spawn secondary explosions while also penetrating, NOT explosive ammo of /// anti-materiel rifles as the explosion just spawns on top of the pawn, not inside the hit body part. /// /// Additionally handles fragmentation effects if defined. /// </summary> /// <param name="instigator">Launcher of the projectile calling the method</param> public virtual void Explode(Thing instigator, IntVec3 pos, Map map) { if (map == null) { Log.Warning("Tried to do explodeCE in a null map."); return; } // Regular explosion stuff if (Props.explosionRadius > 0 && Props.explosionDamage > 0 && parent.def != null && GenGrid.InBounds(pos, map)) { GenExplosion.DoExplosion (pos, map, Props.explosionRadius, Props.explosionDamageDef, instigator, Props.soundExplode == null ? Props.explosionDamageDef.soundExplosion : Props.soundExplode, parent.def, null, Props.postExplosionSpawnThingDef = null, Props.postExplosionSpawnChance = 0f, Props.postExplosionSpawnThingCount = 1, Props.applyDamageToExplosionCellsNeighbors = false, Props.preExplosionSpawnThingDef = null, Props.explosionSpawnChance = 0, Props.preExplosionSpawnThingCount); } // Fragmentation stuff if (!Props.fragments.NullOrEmpty() && GenGrid.InBounds(pos, map)) { if (Props.fragRange <= 0) { Log.Error(parent.LabelCap + " has fragments but no fragRange"); } else { Vector3 exactOrigin = new Vector3(0, 0, 0); exactOrigin.x = parent.DrawPos.x; exactOrigin.z = parent.DrawPos.z; foreach (ThingCountClass fragment in Props.fragments) { for (int i = 0; i < fragment.count; i++) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(fragment.thingDef, null); projectile.canFreeIntercept = true; Vector3 exactTarget = exactOrigin + (new Vector3(1, 0, 1) * UnityEngine.Random.Range(0, Props.fragRange)).RotatedBy(UnityEngine.Random.Range(0, 360)); LocalTargetInfo targetCell = exactTarget.ToIntVec3(); GenSpawn.Spawn(projectile, parent.Position, map); projectile.Launch(instigator, exactOrigin, targetCell, exactTarget, null); } } } } }
private bool TryLaunchCookOffProjectile() { if (AmmoDef.cookOffProjectile == null) { return(false); } Fire fire = Position.GetThingList(Map).Find(f => f.Spawned && f is Fire) as Fire; if (fire != null && fire.fireSize < 0.4) { return(false); } // Spawn projectile if enabled if (!Controller.settings.RealisticCookOff) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(AmmoDef.cookOffProjectile); GenSpawn.Spawn(projectile, Position, Map); // Launch in random direction projectile.canTargetSelf = true; projectile.minCollisionSqr = 0f; projectile.logMisses = false; projectile.Launch(this, new Vector2(DrawPos.x, DrawPos.z), UnityEngine.Random.Range(0, Mathf.PI / 2f), UnityEngine.Random.Range(0, 360), 0.1f, AmmoDef.cookOffProjectile.projectile.speed * AmmoDef.cookOffSpeed, this); } // Create sound and flash effects if (AmmoDef.cookOffFlashScale > 0.01) { MoteMaker.MakeStaticMote(Position, Map, ThingDefOf.Mote_ShotFlash, AmmoDef.cookOffFlashScale); } if (AmmoDef.cookOffSound != null) { AmmoDef.cookOffSound.PlayOneShot(new TargetInfo(Position, Map)); } if (AmmoDef.cookOffTailSound != null) { AmmoDef.cookOffTailSound.PlayOneShotOnCamera(); } return(true); }
private bool TryLaunchCookOffProjectile() { if (AmmoDef == null || AmmoDef.cookOffProjectile == null || Find.Maps.IndexOf(Map) < 0) { return(false); } // Spawn projectile if enabled if (!Controller.settings.RealisticCookOff) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(AmmoDef.cookOffProjectile); GenSpawn.Spawn(projectile, PositionHeld, MapHeld); // Launch in random direction projectile.canTargetSelf = true; projectile.minCollisionDistance = 0f; projectile.logMisses = false; projectile.Launch(this, new Vector2(DrawPos.x, DrawPos.z), Mathf.Acos(2 * UnityEngine.Random.Range(0.5f, 1f) - 1), UnityEngine.Random.Range(0, 360), 0.1f, AmmoDef.cookOffProjectile.projectile.speed * AmmoDef.cookOffSpeed, this); } // Create sound and flash effects if (AmmoDef.cookOffFlashScale > 0.01) { FleckMaker.Static(Position, Map, FleckDefOf.ShotFlash, AmmoDef.cookOffFlashScale); } if (AmmoDef.cookOffSound != null) { AmmoDef.cookOffSound.PlayOneShot(new TargetInfo(Position, Map)); } if (AmmoDef.cookOffTailSound != null) { AmmoDef.cookOffTailSound.PlayOneShotOnCamera(); } 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; 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); }
/// <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> /// Produces a secondary explosion on impact using the explosion values from the projectile's projectile def. Requires the projectile's launcher to be passed on due to protection level. /// Intended use is for HEAT and similar weapons that spawn secondary explosions while also penetrating, NOT explosive ammo of anti-materiel rifles as the explosion just spawns /// on top of the pawn, not inside the hit body part. /// /// Additionally handles fragmentation effects if defined. /// </summary> /// <param name="instigator">Launcher of the projectile calling the method</param> public virtual void Explode(Thing instigator, Vector3 pos, Map map, float scaleFactor = 1) { var posIV = pos.ToIntVec3(); if (map == null) { Log.Warning("Tried to do explodeCE in a null map."); return; } if (!posIV.InBounds(map)) { Log.Warning("Tried to explodeCE out of bounds"); return; } var projCE = parent as ProjectileCE; #region Fragmentation if (!Props.fragments.NullOrEmpty()) { float edificeHeight = (new CollisionVertical(posIV.GetEdifice(map))).Max; Vector2 exactOrigin = new Vector2(pos.x, pos.z); float height; //Fragments fly from a 0 to 45 degree angle away from the explosion var range = new FloatRange(0, Mathf.PI / 8f); if (projCE != null) { height = Mathf.Max(edificeHeight, pos.y); if (edificeHeight < height) { //If the projectile exploded above the ground, they can fly 45 degree away at the bottom as well range.min = -Mathf.PI / 8f; } // TODO : Check for hitting the bottom or top of a roof } else { //Height is not tracked on non-CE projectiles, so we assume this one's on top of the edifice height = edificeHeight; } foreach (ThingCountClass fragment in Props.fragments) { for (int i = 0; i < fragment.count; i++) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(fragment.thingDef, null); GenSpawn.Spawn(projectile, posIV, map); projectile.canTargetSelf = true; projectile.minCollisionSqr = 1f; //TODO : Don't hardcode at FragmentShadowChance, make XML-modifiable projectile.castShadow = (UnityEngine.Random.value < FragmentShadowChance); projectile.logMisses = false; projectile.Launch( instigator, exactOrigin, range.RandomInRange, UnityEngine.Random.Range(0, 360), height, Props.fragSpeedFactor * projectile.def.projectile.speed, projCE ); } } } #endregion // Regular explosion stuff if (Props.explosionRadius > 0 && Props.explosionDamage > 0 && parent.def != null && GenGrid.InBounds(posIV, map)) { // Copy-paste from GenExplosion ExplosionCE explosion = GenSpawn.Spawn(CE_ThingDefOf.ExplosionCE, posIV, map) as ExplosionCE; explosion.height = pos.y; explosion.radius = Props.explosionRadius * scaleFactor; explosion.damType = Props.explosionDamageDef; explosion.instigator = instigator; explosion.damAmount = GenMath.RoundRandom(Props.explosionDamage * scaleFactor); explosion.weapon = null; explosion.projectile = parent.def; explosion.preExplosionSpawnThingDef = Props.preExplosionSpawnThingDef; explosion.preExplosionSpawnChance = Props.preExplosionSpawnChance; explosion.preExplosionSpawnThingCount = Props.preExplosionSpawnThingCount; explosion.postExplosionSpawnThingDef = Props.postExplosionSpawnThingDef; explosion.postExplosionSpawnChance = Props.postExplosionSpawnChance; explosion.postExplosionSpawnThingCount = Props.postExplosionSpawnThingCount; explosion.applyDamageToExplosionCellsNeighbors = Props.applyDamageToExplosionCellsNeighbors; // TODO: for some reason projectile goes to null if (parent.def.projectile != null) { explosion.chanceToStartFire = parent.def.projectile.explosionChanceToStartFire; explosion.dealMoreDamageAtCenter = parent.def.projectile.explosionDealMoreDamageAtCenter; } explosion.StartExplosion(Props.explosionDamageDef.soundExplosion); } }
/// <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> /// Produces a secondary explosion on impact using the explosion values from the projectile's projectile def. Requires the projectile's launcher to be passed on due to protection level. /// Intended use is for HEAT and similar weapons that spawn secondary explosions while also penetrating, NOT explosive ammo of anti-materiel rifles as the explosion just spawns /// on top of the pawn, not inside the hit body part. /// /// Additionally handles fragmentation effects if defined. /// </summary> /// <param name="instigator">Launcher of the projectile calling the method</param> public virtual void Explode(Thing instigator, IntVec3 pos, Map map, float scaleFactor = 1) { if (map == null) { Log.Warning("Tried to do explodeCE in a null map."); return; } if (!pos.InBounds(map)) { Log.Warning("Tried to explodeCE out of bounds"); return; } // Fragmentation stuff if (!Props.fragments.NullOrEmpty() && GenGrid.InBounds(pos, map)) { Vector2 exactOrigin = new Vector2(parent.DrawPos.x, parent.DrawPos.z); float height = (new CollisionVertical(pos.GetEdifice(map))).Max; foreach (ThingCountClass fragment in Props.fragments) { for (int i = 0; i < fragment.count; i++) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(fragment.thingDef, null); GenSpawn.Spawn(projectile, pos, map); var projCE = parent as ProjectileCE; if (projCE != null) { height = Mathf.Max(height, projCE.Height); } projectile.canTargetSelf = true; projectile.minCollisionSqr = 1f; projectile.Launch(instigator, exactOrigin, UnityEngine.Random.Range(0, Mathf.PI / 8f), UnityEngine.Random.Range(0, 360), height, Props.fragSpeedFactor * projectile.def.projectile.speed); } } } // Regular explosion stuff if (Props.explosionRadius > 0 && Props.explosionDamage > 0 && parent.def != null && GenGrid.InBounds(pos, map)) { // Can't use GenExplosion because it no longer allows setting damage amount // Copy-paste from GenExplosion Explosion explosion = new Explosion(); explosion.position = pos; explosion.radius = Props.explosionRadius * scaleFactor; explosion.damType = Props.explosionDamageDef; explosion.instigator = instigator; explosion.damAmount = GenMath.RoundRandom(Props.explosionDamage * scaleFactor); explosion.weaponGear = null; explosion.preExplosionSpawnThingDef = Props.preExplosionSpawnThingDef; explosion.preExplosionSpawnChance = Props.explosionSpawnChance; explosion.preExplosionSpawnThingCount = Props.preExplosionSpawnThingCount; explosion.postExplosionSpawnThingDef = Props.postExplosionSpawnThingDef; explosion.postExplosionSpawnChance = Props.postExplosionSpawnChance; explosion.postExplosionSpawnThingCount = Props.postExplosionSpawnThingCount; explosion.applyDamageToExplosionCellsNeighbors = Props.applyDamageToExplosionCellsNeighbors; map.GetComponent <ExplosionManager>().StartExplosion(explosion, Props.soundExplode ?? Props.explosionDamageDef.soundExplosion); } }
/// <summary> /// Produces a secondary explosion on impact using the explosion values from the projectile's projectile def. Requires the projectile's launcher to be passed on due to protection level. /// Intended use is for HEAT and similar weapons that spawn secondary explosions while also penetrating, NOT explosive ammo of anti-materiel rifles as the explosion just spawns /// on top of the pawn, not inside the hit body part. /// /// Additionally handles fragmentation effects if defined. /// </summary> /// <param name="instigator">Launcher of the projectile calling the method</param> public virtual void Explode(Thing instigator, Vector3 pos, Map map, float scaleFactor = 1) { var posIV = pos.ToIntVec3(); if (map == null) { Log.Warning("Tried to do explodeCE in a null map."); return; } if (!posIV.InBounds(map)) { Log.Warning("Tried to explodeCE out of bounds"); return; } // Fragmentation stuff if (!Props.fragments.NullOrEmpty() && GenGrid.InBounds(posIV, map)) { float edificeHeight = (new CollisionVertical(posIV.GetEdifice(map))).Max; Vector2 exactOrigin = new Vector2(pos.x, pos.z); float height; //Fragments fly from a 0 to 45 degree angle away from the explosion var range = new FloatRange(0, Mathf.PI / 8f); var projCE = parent as ProjectileCE; if (projCE != null) { height = Mathf.Max(edificeHeight, pos.y); if (edificeHeight < height) { //If the projectile exploded above the ground, they can fly 45 degree away at the bottom as well range.min = -Mathf.PI / 8f; } // TODO : Check for hitting the bottom or top of a roof } else { height = edificeHeight; } foreach (ThingCountClass fragment in Props.fragments) { for (int i = 0; i < fragment.count; i++) { ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(fragment.thingDef, null); GenSpawn.Spawn(projectile, posIV, map); projectile.canTargetSelf = true; projectile.minCollisionSqr = 1f; //TODO : Don't hardcode at FragmentShadowChance, make XML-modifiable projectile.castShadow = (UnityEngine.Random.value < FragmentShadowChance); projectile.Launch(instigator, exactOrigin, range.RandomInRange, UnityEngine.Random.Range(0, 360), height, Props.fragSpeedFactor * projectile.def.projectile.speed); } } } // Regular explosion stuff if (Props.explosionRadius > 0 && Props.explosionDamage > 0 && parent.def != null && GenGrid.InBounds(posIV, map)) { // Can't use GenExplosion because it no longer allows setting damage amount // Copy-paste from GenExplosion Explosion explosion = new Explosion(); explosion.position = posIV; explosion.radius = Props.explosionRadius * scaleFactor; explosion.damType = Props.explosionDamageDef; explosion.instigator = instigator; explosion.damAmount = GenMath.RoundRandom(Props.explosionDamage * scaleFactor); explosion.weaponGear = null; explosion.preExplosionSpawnThingDef = Props.preExplosionSpawnThingDef; explosion.preExplosionSpawnChance = Props.explosionSpawnChance; explosion.preExplosionSpawnThingCount = Props.preExplosionSpawnThingCount; explosion.postExplosionSpawnThingDef = Props.postExplosionSpawnThingDef; explosion.postExplosionSpawnChance = Props.postExplosionSpawnChance; explosion.postExplosionSpawnThingCount = Props.postExplosionSpawnThingCount; explosion.applyDamageToExplosionCellsNeighbors = Props.applyDamageToExplosionCellsNeighbors; map.GetComponent <ExplosionManager>().StartExplosion(explosion, Props.soundExplode ?? Props.explosionDamageDef.soundExplosion); } }