/// <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 (half of a circle) to 45 (3/4 of a circle) degree angle away from the explosion var range = new FloatRange(0.5f, 0.75f); 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 (1/4 of a circle) at the bottom as well range.min = 0.25f; } // 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 (ThingDefCountClass 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, Mathf.Acos(2 * range.RandomInRange - 1), 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.damageFalloff = parent.def.projectile.explosionDamageFalloff; } explosion.StartExplosion(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); } }
/// <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); } 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 = Mathf.Min(1.5f, targetDistance * 0.75f); 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); 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; } /// 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); }