protected virtual void Explode() { ExplosionCE explosion = GenSpawn.Spawn(CE_ThingDefOf.ExplosionCE, ExactPosition.ToIntVec3(), Map) as ExplosionCE; explosion.height = ExactPosition.y; explosion.radius = def.projectile.explosionRadius; explosion.damType = def.projectile.damageDef; explosion.instigator = launcher; explosion.damAmount = def.projectile.GetDamageAmount(1); explosion.weapon = equipmentDef; explosion.projectile = def; explosion.preExplosionSpawnThingDef = def.projectile.preExplosionSpawnThingDef; explosion.preExplosionSpawnChance = def.projectile.preExplosionSpawnChance; explosion.preExplosionSpawnThingCount = def.projectile.preExplosionSpawnThingCount; explosion.postExplosionSpawnThingDef = def.projectile.postExplosionSpawnThingDef; explosion.postExplosionSpawnChance = def.projectile.postExplosionSpawnChance; explosion.postExplosionSpawnThingCount = def.projectile.postExplosionSpawnThingCount; explosion.applyDamageToExplosionCellsNeighbors = def.projectile.applyDamageToExplosionCellsNeighbors; explosion.chanceToStartFire = def.projectile.explosionChanceToStartFire; explosion.damageFalloff = def.projectile.explosionDamageFalloff; explosion.StartExplosion(def.projectile.soundExplode); explosion.armorPenetration = explosion.damAmount * 0.1f; //This code was disabled because it didn't run under previous circumstances. Could be enabled if necessary /* * if (map != null && base.ExactPosition.ToIntVec3().IsValid) * { * ThrowBigExplode(base.ExactPosition + Gen.RandomHorizontalVector(def.projectile.explosionRadius * 0.5f), base.Map, def.projectile.explosionRadius * 0.4f); * } */ base.Impact(null); // base.Impact() handles this.Destroy() and comp.Explode() }
/// <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; } if (!Props.fragments.NullOrEmpty()) { var projCE = parent as ProjectileCE; var edifice = posIV.GetEdifice(map); var edificeHeight = edifice == null ? 0 : new CollisionVertical(edifice).Max; var height = projCE != null?Mathf.Max(edificeHeight, pos.y) : edificeHeight; foreach (var fragment in Props.fragments) { _monoDummy.GetComponent <MonoDummy>().StartCoroutine(FragRoutine(pos, map, height, instigator, fragment, Props.fragSpeedFactor)); } } // 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; explosion.armorPenetration = explosion.damAmount * 0.1f; explosion.damageFalloff = Props.damageFalloff; explosion.chanceToStartFire = Props.chanceToStartFire; explosion.StartExplosion(Props.soundExplode ?? Props.explosionDamageDef.soundExplosion); } }
public const float MaxExplosionScale = 10f; //as if 1000 shells exploded public static void DoExplosion(IntVec3 center, Map map, float radius, DamageDef damType, Thing instigator, int damAmount = -1, float armorPenetration = -1f, SoundDef explosionSound = null, ThingDef weapon = null, ThingDef projectile = null, Thing intendedTarget = null, ThingDef postExplosionSpawnThingDef = null, float postExplosionSpawnChance = 0f, int postExplosionSpawnThingCount = 1, bool applyDamageToExplosionCellsNeighbors = false, ThingDef preExplosionSpawnThingDef = null, float preExplosionSpawnChance = 0f, int preExplosionSpawnThingCount = 1, float chanceToStartFire = 0f, bool damageFalloff = false, float?direction = null, List <Thing> ignoredThings = null, float height = 0f, float scaleFactor = 1f, bool destroyAfterwards = false, ThingWithComps explosionParentToDestroy = null) { // Allows destroyed things to be exploded with appropriate scaleFactor if (scaleFactor <= 0f) { scaleFactor = 1f; } else { scaleFactor = Mathf.Clamp(scaleFactor, MinExplosionScale, MaxExplosionScale); } if (map == null) { Log.Warning("CombatExtended :: Tried to do explosionCE in a null map."); return; } if (damAmount < 0) { damAmount = damType.defaultDamage; armorPenetration = damType.defaultArmorPenetration; if (damAmount < 0) { Log.ErrorOnce("CombatExtended :: Attempted to trigger an explosionCE without defined damage", 910948823); damAmount = 1; } } explosionSound = explosionSound ?? damType.soundExplosion; if (explosionSound == null) { Log.Error("CombatExtended :: SoundDef was null for DamageDef " + damType.defName + " as well as instigator " + instigator.ThingID); } damAmount = Mathf.RoundToInt(damAmount * scaleFactor); radius *= scaleFactor; armorPenetration *= scaleFactor; ExplosionCE explosion = GenSpawn.Spawn(CE_ThingDefOf.ExplosionCE, center, map) as ExplosionCE; IntVec3? needLOSToCell = null; IntVec3? needLOSToCell2 = null; if (direction.HasValue) { CalculateNeededLOSToCells(center, map, direction.Value, out needLOSToCell, out needLOSToCell2); } explosion.height = height; explosion.radius = radius; explosion.damType = damType; explosion.instigator = instigator; explosion.damAmount = damAmount; explosion.armorPenetration = armorPenetration; explosion.weapon = weapon; explosion.projectile = projectile; explosion.intendedTarget = intendedTarget; explosion.preExplosionSpawnThingDef = preExplosionSpawnThingDef; explosion.preExplosionSpawnChance = preExplosionSpawnChance; explosion.preExplosionSpawnThingCount = preExplosionSpawnThingCount; explosion.postExplosionSpawnThingDef = postExplosionSpawnThingDef; explosion.postExplosionSpawnChance = postExplosionSpawnChance; explosion.postExplosionSpawnThingCount = postExplosionSpawnThingCount; explosion.applyDamageToExplosionCellsNeighbors = applyDamageToExplosionCellsNeighbors; explosion.chanceToStartFire = chanceToStartFire; explosion.damageFalloff = damageFalloff; explosion.needLOSToCell1 = needLOSToCell; explosion.needLOSToCell2 = needLOSToCell2; explosion.StartExplosionCE(explosionSound, ignoredThings); // Needed to allow CompExplosive to use stackCount if (destroyAfterwards && !explosionParentToDestroy.Destroyed) { explosionParentToDestroy?.Kill(); } }
/// <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); } }
public void StartExplosionCE(SoundDef explosionSound, List <Thing> ignoredThings) //Removed cellsToAffect by Worker { if (Controller.settings.MergeExplosions) { ExplosionCE existingExplosion = Position.GetThingList(Map).FirstOrDefault(x => x.def == CE_ThingDefOf.ExplosionCE && x != this && !(x as ExplosionCE).toBeMerged) as ExplosionCE; if (existingExplosion == null) { var i = 1; var num = GenRadial.NumCellsInRadius(MaxMergeRange); while (i < num && existingExplosion == null) { IntVec3 c = Position + GenRadial.RadialPattern[i]; if (c.InBounds(Map)) { existingExplosion = c.GetThingList(Map).FirstOrDefault(x => x.def == CE_ThingDefOf.ExplosionCE && x != this && !(x as ExplosionCE).toBeMerged) as ExplosionCE; } i++; } } if (existingExplosion != null) { if (MergeWith(existingExplosion, out var merged, out var nonMerged)) { nonMerged.toBeMerged = true; //Was possible to merge, destroy nonmerged (later) and start merged. merged.RestartAfterMerge(explosionSound); return; } } } if (!Spawned) { Log.Error("Called StartExplosion() on unspawned thing."); return; } if (this.ignoredThings.NullOrEmpty()) { this.ignoredThings = ignoredThings; } startTick = Find.TickManager.TicksGame; cellsToAffect.Clear(); damagedThings.Clear(); addedCellsAffectedOnlyByDamage.Clear(); //this.cellsToAffect.AddRange(this.damType.Worker.ExplosionCellsToHit(this)); cellsToAffect.AddRange(ExplosionCellsToHit); if (applyDamageToExplosionCellsNeighbors) { AddCellsNeighbors(cellsToAffect); } damType.Worker.ExplosionStart(this, cellsToAffect); PlayExplosionSound(explosionSound); FleckMaker.WaterSplash(Position.ToVector3Shifted(), Map, radius * 6f, 20f); cellsToAffect.Sort((IntVec3 a, IntVec3 b) => GetCellAffectTick(b).CompareTo(GetCellAffectTick(a))); RegionTraverser.BreadthFirstTraverse(Position, Map, (Region from, Region to) => true, delegate(Region x) { var list = x.ListerThings.ThingsInGroup(ThingRequestGroup.Pawn); for (var i = list.Count - 1; i >= 0; i--) { ((Pawn)list[i]).mindState.Notify_Explosion(this); } return(false); }, 25, RegionType.Set_Passable); }
// New method public virtual bool MergeWith(ExplosionCE other, out ExplosionCE merged, out ExplosionCE nonMerged) { merged = null; nonMerged = null; //Log.Message("This: "+this.ThingID+", Other: "+other.ThingID); //Sanity check if (other == null) { return(false); } var startDiff = (other.startTick == 0 ? Find.TickManager.TicksGame : other.startTick) - (startTick == 0 ? Find.TickManager.TicksGame : startTick); //Log.Message("StartDiff: " + startDiff); if (Mathf.Abs(startDiff) > MaxMergeTicks) { return(false); } //Log.Message("DF: " + damageFalloff + ", " + other.damageFalloff); if (other.damageFalloff != damageFalloff) { return(false); } //Log.Message("DT: " + damType.defName + ", " + other.damType.defName); if (other.damType != damType) { return(false); } //Log.Message("Sign: " + Mathf.Sign(height - CollisionVertical.WallCollisionHeight) + ", " + Mathf.Sign(other.height - CollisionVertical.WallCollisionHeight)); if (Mathf.Sign(other.height - CollisionVertical.WallCollisionHeight) != Mathf.Sign(height - CollisionVertical.WallCollisionHeight)) { return(false); } //Log.Message("PreExp: " + (preExplosionSpawnThingDef != null ? preExplosionSpawnThingDef.defName : "null") + ", " + (other.preExplosionSpawnThingDef != null ? other.preExplosionSpawnThingDef.defName : "null")); if (preExplosionSpawnThingDef != null && other.preExplosionSpawnThingDef != null && other.preExplosionSpawnThingDef != preExplosionSpawnThingDef) { return(false); } //Log.Message("PostExp: " + (postExplosionSpawnThingDef != null ? postExplosionSpawnThingDef.defName : "null") + ", " + (other.postExplosionSpawnThingDef != null ? other.postExplosionSpawnThingDef.defName : "null")); if (postExplosionSpawnThingDef != null && other.postExplosionSpawnThingDef != null && other.postExplosionSpawnThingDef != postExplosionSpawnThingDef) { return(false); } Thing newInstigator = null; //Log.Message("Instig: " + (instigator != null ? instigator.ThingID : "null") + ", " + (other.instigator != null ? other.instigator.ThingID : "null")); //Instigator "equal-enough" if (other.instigator != null && instigator != null) { //If both were hostile action.. if (other.intendedTarget != null && intendedTarget != null && other.intendedTarget.HostileTo(other.instigator) && intendedTarget.HostileTo(instigator)) { //If both instigators had different factions, the explosions had different intentions -- cannot merge if (instigator.Faction != null && other.instigator.Faction != null && instigator.Faction != other.instigator.Faction) { return(false); } } else { if (other.instigator != instigator) { //Impossible to distinguish for a combat log which pawn initiated the explosion if (instigator is Pawn && other.instigator is Pawn) { return(false); } else { if (instigator is Pawn) { newInstigator = instigator; } else if (other.instigator is Pawn) { newInstigator = other.instigator; } if (instigator is AmmoThing || other.instigator is AmmoThing) { newInstigator = instigator is AmmoThing ? other.instigator : instigator; } else { newInstigator = Rand.Value < 0.5f ? instigator : other.instigator; } } } } } //Log.Message("Wpn: " + (weapon != null ? weapon.defName : "null") + ", " + (other.weapon != null ? other.weapon.defName : "null")); //Might be problematic if (other.weapon != null && weapon != null && other.weapon != weapon) { return(false); } //Log.Message("Prj: " + (projectile != null ? projectile.defName : "null") + ", " + (other.projectile != null ? other.projectile.defName : "null")); //Might be problematic if (other.projectile != null && projectile != null && other.projectile != projectile) { return(false); } //Log.Message("LOS1: " + needLOSToCell1.ToString() + ", " + other.needLOSToCell1.ToString()); if (other.needLOSToCell1 != needLOSToCell1) { return(false); } //Log.Message("LOS2: " + needLOSToCell2.ToString() + ", " + other.needLOSToCell2.ToString()); if (other.needLOSToCell2 != needLOSToCell2) { return(false); } //Crucial matches merged = startDiff <= 0 ? this : other; nonMerged = startDiff <= 0 ? other : this; //Log.Message("this: "+this.ThingID + " merged: "+merged.ThingID + " other: "+other.ThingID + " nonmerged: "+nonMerged.ThingID); merged.instigator = newInstigator; //Combine shared ignored things var newIgnoredThings = new HashSet <Thing>(); if (ignoredThings != null && other.ignoredThings != null) { newIgnoredThings.AddRange(ignoredThings.Where(x => other.ignoredThings.Contains(x))); } //Add instigators if necessary if (ignoredThings != null && ignoredThings.Contains(instigator)) { newIgnoredThings.Add(instigator); } if (other.ignoredThings != null && other.ignoredThings.Contains(other.instigator)) { newIgnoredThings.Add(other.instigator); } merged.ignoredThings = newIgnoredThings.ToList(); //Combine chances such that the same spread of things is observed, while the average is retained to the best of our ability using integer values only merged.chanceToStartFire = 1 - (1 - chanceToStartFire) * (1 - other.chanceToStartFire); merged.preExplosionSpawnThingCount = Mathf.RoundToInt((preExplosionSpawnThingCount * preExplosionSpawnChance + other.preExplosionSpawnThingCount * other.preExplosionSpawnChance) / (1 - (1 - preExplosionSpawnChance) * (1 - other.preExplosionSpawnChance))); merged.preExplosionSpawnChance = 1 - (1 - preExplosionSpawnChance) * (1 - other.preExplosionSpawnChance); merged.postExplosionSpawnThingCount = Mathf.RoundToInt((postExplosionSpawnThingCount * postExplosionSpawnChance + other.postExplosionSpawnThingCount * other.postExplosionSpawnChance) / (1 - (1 - postExplosionSpawnChance) * (1 - other.postExplosionSpawnChance))); merged.postExplosionSpawnChance = 1 - (1 - postExplosionSpawnChance) * (1 - other.postExplosionSpawnChance); //Linearly combine damage, since that's how it would be if both explosions ran merged.armorPenetration = Mathf.Max(damAmount * PressurePerDamage, armorPenetration) + Mathf.Max(other.damAmount * PressurePerDamage, other.armorPenetration); merged.damAmount = damAmount + other.damAmount; if (!merged.applyDamageToExplosionCellsNeighbors && nonMerged.applyDamageToExplosionCellsNeighbors) { merged.applyDamageToExplosionCellsNeighbors = true; merged.radiusChange = true; } if (radius != other.radius && merged.radius < nonMerged.radius) { merged.radius = Mathf.Max(radius, other.radius); merged.radiusChange = true; } return(true); }