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
{