diff --git a/Defs/WorldObjectDefs/WorldObjects.xml b/Defs/WorldObjectDefs/WorldObjects.xml index efbbe483a4..41ad52f126 100755 --- a/Defs/WorldObjectDefs/WorldObjects.xml +++ b/Defs/WorldObjectDefs/WorldObjects.xml @@ -23,4 +23,14 @@ Things/WorldObjects/Munitions/Shell + + TravelingRaycast + + An artillery beam. + CombatExtended.TravelingRaycast + Things/WorldObjects/Munitions/Shell_Invisible + true + false + + \ No newline at end of file diff --git a/Source/CombatExtended/CombatExtended/DefOfs/CE_WorldObjectDefOf.cs b/Source/CombatExtended/CombatExtended/DefOfs/CE_WorldObjectDefOf.cs index 93859c1920..57dd816612 100755 --- a/Source/CombatExtended/CombatExtended/DefOfs/CE_WorldObjectDefOf.cs +++ b/Source/CombatExtended/CombatExtended/DefOfs/CE_WorldObjectDefOf.cs @@ -9,4 +9,5 @@ static CE_WorldObjectDefOf() DefOfHelper.EnsureInitializedInCtor(typeof(CE_WorldObjectDefOf)); } public static WorldObjectDef TravelingShell; + public static WorldObjectDef TravelingRaycast; } diff --git a/Source/CombatExtended/CombatExtended/Lasers/LaserBeamCE.cs b/Source/CombatExtended/CombatExtended/Lasers/LaserBeamCE.cs index 5039ef8745..9410198447 100755 --- a/Source/CombatExtended/CombatExtended/Lasers/LaserBeamCE.cs +++ b/Source/CombatExtended/CombatExtended/Lasers/LaserBeamCE.cs @@ -45,7 +45,7 @@ void TriggerEffect(EffecterDef effect, IntVec3 dest) effecter.Cleanup(); } - public void SpawnBeam(Vector3 a, Vector3 b) + public virtual void SpawnBeam(Vector3 a, Vector3 b) { LaserBeamGraphicCE graphic = ThingMaker.MakeThing(laserBeamDef.beamGraphic, null) as LaserBeamGraphicCE; if (graphic == null) diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index e8d917f056..0e60667be1 100755 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -469,7 +469,19 @@ public virtual void Throw(Thing launcher, Vector3 origin, Vector3 heading, Thing #endregion #region Raycast - 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) + 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, + bool useSameHeight = false + ) { float magicSpreadFactor = Mathf.Sin(0.06f / 2 * Mathf.Deg2Rad) + aperatureSize; @@ -522,6 +534,11 @@ public virtual void RayCast(Thing launcher, VerbProperties verbProps, Vector2 or LastPos = destination; Position = ExactPosition.ToIntVec3(); + if (useSameHeight) + { + muzzle.y = destination.y; + } + lbce.SpawnBeam(muzzle, destination); RayCastSuppression(muzzle.ToIntVec3(), destination.ToIntVec3()); lbce.Impact(null, muzzle); @@ -564,9 +581,13 @@ public virtual void RayCast(Thing launcher, VerbProperties verbProps, Vector2 or LastPos = destination; Position = ExactPosition.ToIntVec3(); + if (useSameHeight) + { + muzzle.y = destination.y; + } + lbce.SpawnBeam(muzzle, destination); RayCastSuppression(muzzle.ToIntVec3(), destination.ToIntVec3()); - lbce.Impact(thing, muzzle); return; @@ -576,6 +597,11 @@ public virtual void RayCast(Thing launcher, VerbProperties verbProps, Vector2 or } if (lbce != null) { + if (useSameHeight) + { + muzzle.y = destination.y; + } + lbce.SpawnBeam(muzzle, destination); RayCastSuppression(muzzle.ToIntVec3(), destination.ToIntVec3()); Destroy(DestroyMode.Vanish); @@ -583,6 +609,79 @@ public virtual void RayCast(Thing launcher, VerbProperties verbProps, Vector2 or } } + public void RayCastWorldTarget( + Thing launcher, + Verb_ShootCE verbToUse, + Vector2 originLocal, + float shotAngle, + float shotHeight, + float shotSpeed = -1f, + float spreadDegrees = 0f, + float aperatureSize = 0.03f, + Thing equipment = null + ) + { + if (!globalTargetInfo.IsValid) + { + Log.Warning("Cannot Raycast on a world target without globalTargetInfo"); + return; + } + + // --- Graphical part + + // Let's fire only on the exit cell + Vector3 u = verbToUse.Caster.TrueCenter(); + Vector3 v = verbToUse.currentTarget.Cell.ToVector3Shifted(); + var d = v - u; + var precisedShotRotation = (-90 + Mathf.Rad2Deg * Mathf.Atan2(d.z, d.x)) % 360; + + // create the local raycast + this.RayCast( + launcher, + verbToUse.verbProps, + originLocal, + 0, // set angle to 0 so the raycast goes straight (it won't touch anything so it doesn't matter) + precisedShotRotation, + shotHeight, + shotSpeed, + 0, // no need + 0, // no need + equipment + ); + + // --- Creating shell to use linked mechanics + + Props.shellingProps.tilesPerTick = 99999; // instant speeeeed ! + + TravelingRaycast travelingRaycast = (TravelingRaycast)WorldObjectMaker.MakeWorldObject(CE_WorldObjectDefOf.TravelingRaycast); + if (launcher?.Faction != null) + { + travelingRaycast.SetFaction(launcher.Faction); + } + travelingRaycast.Tile = launcher.Map.Tile; + travelingRaycast.SpawnSetup(); + Find.World.worldObjects.Add(travelingRaycast); + travelingRaycast.launcher = launcher; + travelingRaycast.equipmentDef = equipmentDef; + travelingRaycast.globalSource = new GlobalTargetInfo(OriginIV3, launcher.Map); + travelingRaycast.globalSource.tileInt = launcher.Map.Tile; + travelingRaycast.globalSource.mapInt = launcher.Map; + travelingRaycast.globalSource.worldObjectInt = launcher.Map.Parent; + travelingRaycast.shellDef = def; + travelingRaycast.globalTarget = globalTargetInfo; + + travelingRaycast.verbToUse = verbToUse; + travelingRaycast.spreadDegrees = spreadDegrees; + travelingRaycast.aperatureSize = aperatureSize; + travelingRaycast.equipement = equipment; + + if (!travelingRaycast.TryTravel(launcher.Map.Tile, globalTargetInfo.Tile)) + { + Log.Error($"CE: Travling raycast {this.def} failed to launch!"); + travelingRaycast.Destroy(); + } + } + protected void RayCastSuppression(IntVec3 muzzle, IntVec3 destination, Map map = null) { if (muzzle == destination) diff --git a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs index 6f5b00781e..126eb411e4 100755 --- a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs +++ b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs @@ -153,7 +153,7 @@ public CompFireModes CompFireModes return compFireModes; } } - private ProjectilePropertiesCE ProjectileProps => (ProjectilePropertiesCE)compAmmo?.CurAmmoProjectile?.projectile ?? null; + private ProjectilePropertiesCE ProjectileProps => (ProjectilePropertiesCE)Projectile?.projectile; public float MaxWorldRange => ProjectileProps?.shellingProps.range ?? -1f; public bool EmptyMagazine => CompAmmo?.EmptyMagazine ?? false; public bool FullMagazine => CompAmmo?.FullMagazine ?? false; @@ -740,7 +740,7 @@ public override IEnumerable GetGizmos() // Modified yield return com; } } - if (IsMortar && Active && Faction.IsPlayerSafe() && (compAmmo?.UseAmmo ?? false) && ProjectileProps?.shellingProps != null) + if (IsMortar && Active && Faction.IsPlayerSafe() && ProjectileProps?.shellingProps != null) { Command_ArtilleryTarget wt = new Command_ArtilleryTarget() { diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs index 496c2939c4..98ca068bea 100755 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootCE.cs @@ -131,6 +131,22 @@ public float AimAngle public override ThingDef Projectile => CompAmmo?.CurrentAmmo != null ? CompAmmo.CurAmmoProjectile : base.Projectile; + public override float ShotHeight + { + get + { + if (projectilePropsCE.isInstant && projectilePropsCE.flyOverhead) + { + // - Set the height to be above roofs as a instant projectile with flyOverhead should bypass roofs. Think big lasers for example. + // - Equivelant to 7m + // - Use for VGE patch + // - see also CombatExtended.Compatibility.SOS2Compat.Verb_ShootShip_CE.ShotHeight + return 4f; + } + return base.ShotHeight; + } + } + #endregion #region Methods diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs index f0433f3438..f086e952ee 100644 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs @@ -163,9 +163,13 @@ public virtual bool TryCastGlobalShot() return false; } bool instant = false; + float spreadDegrees = 0; + float aperatureSize = 0; if (Projectile.projectile is ProjectilePropertiesCE pprop) { instant = pprop.isInstant; + spreadDegrees = (EquipmentSource?.GetStatValue(CE_StatDefOf.ShotSpread) ?? 0) * pprop.spreadMult; + aperatureSize = 0.03f; } ShiftVecReport reportGlobal = ShiftVecReportFor(globalTargetInfo); @@ -180,6 +184,7 @@ public virtual bool TryCastGlobalShot() ShiftTarget(report, pelletMechanicsOnly, instant); float shotSpeed = ShotSpeed * 5; + var intendedTarget = globalTargetInfo.Thing ?? currentTarget; //The projectile shouldn't care about the target thing projectile.globalTargetInfo = new GlobalTargetInfo(); projectile.globalTargetInfo.cellInt = shiftedGlobalCell; @@ -191,15 +196,31 @@ public virtual bool TryCastGlobalShot() projectile.globalSourceInfo = globalSourceInfo; 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; + + if (instant) + { + projectile.RayCastWorldTarget( + Shooter, + this, + sourceLoc, + shotAngle, + 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); + } } /* diff --git a/Source/CombatExtended/CombatExtended/WorldObjects/TravelingRaycast.cs b/Source/CombatExtended/CombatExtended/WorldObjects/TravelingRaycast.cs new file mode 100644 index 0000000000..11deaeefdb --- /dev/null +++ b/Source/CombatExtended/CombatExtended/WorldObjects/TravelingRaycast.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using Verse; + +namespace CombatExtended; + +public class TravelingRaycast : TravelingShell +{ + public Verb_ShootCE verbToUse; + public float spreadDegrees; + public float aperatureSize; + public Thing equipement; + + protected override void LaunchProjectile(IntVec3 sourceCell, LocalTargetInfo target, Map map, float shotSpeed = 20, float shotHeight = 200) + { + Vector3 source = new Vector3(sourceCell.x, shotHeight, sourceCell.z); + Vector3 targetPos = target.Cell.ToVector3Shifted(); + + ProjectileCE projectile = (ProjectileCE)ThingMaker.MakeThing(shellDef); + ProjectilePropertiesCE pprops = projectile.def.projectile as ProjectilePropertiesCE; + float shotRotation = pprops.TrajectoryWorker.ShotRotation(pprops, source, targetPos); + float shotAngle = pprops.TrajectoryWorker.ShotAngle(pprops, source, targetPos, shotSpeed); + + projectile.canTargetSelf = false; + projectile.Position = sourceCell; + projectile.SpawnSetup(map, false); + + if (pprops.isInstant) + { + if (verbToUse == null) + { + Log.Warning("Instant shelling needs a ShootingCE Verb in order to work."); + return; + } + + float tsa = verbToUse.AdjustShotHeight(launcher, target, ref shotHeight); + projectile.RayCast(launcher, + verbToUse.verbProps, + new Vector2(source.x, source.z), + shotAngle, + shotRotation, + shotHeight + tsa, + shotSpeed, + spreadDegrees, + aperatureSize, + null, + true // Allow beam to be drawn correctly + ); + } + else + { + // classic shell behavior + Log.Warning("TravellingRaycast called for a classic projectile. Aborted."); + } + } +} diff --git a/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShell.cs b/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShell.cs index 8f82c4d961..004e489244 100755 --- a/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShell.cs +++ b/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShell.cs @@ -44,6 +44,7 @@ public override bool ExpandingIconFlipHorizontal { get => GenWorldUI.WorldToUIPosition(Start).x > GenWorldUI.WorldToUIPosition(End).x; } + public bool IsInstant => (shellDef.projectile as ProjectilePropertiesCE).isInstant; public override float ExpandingIconRotation { @@ -124,12 +125,8 @@ private bool TryShell(WorldObject worldObject) Ray ray = new Ray(targetCell.ToVector3(), -1 * direction); Bounds mapBounds = new Bounds((mapSize / 2f).Yto0(), mapSize); mapBounds.IntersectRay(ray, out float distanceToEdge); - IntVec3 sourceCell = ray.GetPoint(distanceToEdge * 0.75f).ToIntVec3(); - LaunchProjectile( - sourceCell, - targetCell, - map: map, - shotSpeed: 55f); + IntVec3 sourceCell = ray.GetPoint(distanceToEdge * (IsInstant ? 1f : 0.75f)).ToIntVec3(); // Instant shells should start at the edge of the map + LaunchProjectile(sourceCell, targetCell, map); } WorldObjects.HostilityComp hostility = worldObject.GetComponent(); WorldObjects.HealthComp healthComp = worldObject.GetComponent(); @@ -148,7 +145,7 @@ private bool TryShell(WorldObject worldObject) return shelled; } - private void LaunchProjectile(IntVec3 sourceCell, LocalTargetInfo target, Map map, float shotSpeed = 20, float shotHeight = 200) + protected virtual void LaunchProjectile(IntVec3 sourceCell, LocalTargetInfo target, Map map, float shotSpeed = 20, float shotHeight = 200) { Vector3 source = new Vector3(sourceCell.x, shotHeight, sourceCell.z); Vector3 targetPos = target.Cell.ToVector3Shifted(); diff --git a/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShellProperties.cs b/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShellProperties.cs index 81a5583459..5252710f6c 100755 --- a/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShellProperties.cs +++ b/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShellProperties.cs @@ -23,6 +23,11 @@ public class TravelingShellProperties /// public string iconPath; + /// + /// Projectile arrive at the same height and same speed they're launched. + /// + public bool arrivedAtSameProps = false; + public Type workerClass = typeof(WorldObjectDamageWorker); public WorldObjectDamageWorker Worker {