diff --git a/Languages/English/Keyed/Messages.xml b/Languages/English/Keyed/Messages.xml index d906df7b51..da165942ad 100644 --- a/Languages/English/Keyed/Messages.xml +++ b/Languages/English/Keyed/Messages.xml @@ -14,4 +14,6 @@ Artillery fire incoming from {0} ({1}). A group from {0} is planning to raid {1} at {2} soon in retaliation for recent events. + Cannot fire on different layer tile with this piece of artillery. + \ No newline at end of file diff --git a/Source/BetterTurretsCompat/BetterTurretsCompat.csproj b/Source/BetterTurretsCompat/BetterTurretsCompat.csproj index 4f85faa002..329e7019eb 100644 --- a/Source/BetterTurretsCompat/BetterTurretsCompat.csproj +++ b/Source/BetterTurretsCompat/BetterTurretsCompat.csproj @@ -48,7 +48,7 @@ - + diff --git a/Source/CombatExtended/CombatExtended.csproj b/Source/CombatExtended/CombatExtended.csproj index 57f60f9a1c..27823e2ec8 100644 --- a/Source/CombatExtended/CombatExtended.csproj +++ b/Source/CombatExtended/CombatExtended.csproj @@ -118,7 +118,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Source/CombatExtended/CombatExtended/Comps/CompOrbitalTurret.cs b/Source/CombatExtended/CombatExtended/Comps/CompOrbitalTurret.cs new file mode 100644 index 0000000000..b4ad17c756 --- /dev/null +++ b/Source/CombatExtended/CombatExtended/Comps/CompOrbitalTurret.cs @@ -0,0 +1,8 @@ +using Verse; + +namespace CombatExtended; + +public class CompOrbitalTurret : ThingComp +{ + public CompProperties_OrbitalTurret Props => (CompProperties_OrbitalTurret)props; +} diff --git a/Source/CombatExtended/CombatExtended/Comps/CompProperties_OrbitalTurret.cs b/Source/CombatExtended/CombatExtended/Comps/CompProperties_OrbitalTurret.cs new file mode 100644 index 0000000000..484540cf07 --- /dev/null +++ b/Source/CombatExtended/CombatExtended/Comps/CompProperties_OrbitalTurret.cs @@ -0,0 +1,15 @@ + +using Verse; + +namespace CombatExtended; + +public class CompProperties_OrbitalTurret: CompProperties +{ + public float interLayerPrecisionBonusFactor = 1; + public bool isMarkMandatory = false; + + public CompProperties_OrbitalTurret() + { + compClass = typeof(CompOrbitalTurret); + } +} diff --git a/Source/CombatExtended/CombatExtended/Gizmos/Command_ArtilleryTarget.cs b/Source/CombatExtended/CombatExtended/Gizmos/Command_ArtilleryTarget.cs index 08f0142ec4..c623e0f1b2 100755 --- a/Source/CombatExtended/CombatExtended/Gizmos/Command_ArtilleryTarget.cs +++ b/Source/CombatExtended/CombatExtended/Gizmos/Command_ArtilleryTarget.cs @@ -1,22 +1,39 @@ -using System; -using System.Collections.Generic; -using System.Linq; using RimWorld; using RimWorld.Planet; +using System.Collections.Generic; +using System.Linq; using UnityEngine; using Verse; namespace CombatExtended; + public class Command_ArtilleryTarget : Command { + #region Fields + public Building_TurretGunCE turret; public List others = null; + #endregion + + #region Properties + + bool CanShootOtherLayers => turret.CompOrbitalTurret != null; + ///// + ///// When firing on orbital targets, it can be tricky to use binoculars ... + ///// This disables the need of target mark. + ///// + bool MandatoryMarkToFireOutBounds => turret.CompOrbitalTurret?.Props.isMarkMandatory ?? true; + public IEnumerable SelectedTurrets => others?.Select(o => o.turret) ?? new List() { turret }; public override bool GroupsWith(Gizmo other) => other is Command_ArtilleryTarget; + #endregion + + #region Methods + public override void MergeWith(Gizmo other) { var order = other as Command_ArtilleryTarget; @@ -28,6 +45,11 @@ public override void MergeWith(Gizmo other) others.Add(order); } + protected void CommandProcessInput(Event ev) + { + base.ProcessInput(ev); + } + public override void ProcessInput(Event ev) { CameraJumper.TryJump(CameraJumper.GetWorldTarget(turret)); @@ -43,160 +65,227 @@ public override void ProcessInput(Event ev) Log.Error("Command_ArtilleryTarget selected turrets collection is invalid"); return; } - int turretTile = turret.Map.Tile; - int radius = (int)turret.MaxWorldRange; - Find.WorldTargeter.BeginTargeting((targetInfo) => - { - IEnumerable turrets = SelectedTurrets; - Map map = Find.World.worldObjects.MapParentAt(targetInfo.Tile)?.Map ?? null; - // We only want player to target world object when there's no colonist in the map - if (map != null && map.mapPawns.AnyPawnBlockingMapRemoval) + PlanetTile turretTile = turret.Map.Tile; + int radius = Mathf.FloorToInt(turret.MaxWorldRange); + + ShellingUtility.ClearRadiusCache(); + + Find.WorldTargeter.BeginTargeting( + action: (GlobalTargetInfo targetInfo) => { - IntVec3 selectedCell = IntVec3.Invalid; - Find.WorldTargeter.StopTargeting(); - CameraJumper.TryJumpInternal(new IntVec3((int)map.Size.x / 2, 0, (int)map.Size.z / 2), map, CameraJumper.MovementMode.Pan); - Find.Targeter.BeginTargeting(new TargetingParameters() + // Check additionnal condition + if (!AdditionnalTargettingCondition(targetInfo)) { - canTargetLocations = true, - canTargetBuildings = true, - canTargetHumans = true - }, (target) => + return false; + } + + IEnumerable turrets = SelectedTurrets; + Map map = Find.World.worldObjects.MapParentAt(targetInfo.Tile)?.Map ?? null; + + if (!CanShootOtherLayers && targetInfo.Tile.Layer != turretTile.Layer) { - targetInfo.mapInt = map; - targetInfo.tileInt = map.Tile; - targetInfo.cellInt = target.cellInt; - //targetInfo.thingInt = target.thingInt; - TryAttack(turrets, targetInfo, target); - }, highlightAction: (target) => + Messages.Message("CE_Message_ArtilleryBadLayer".Translate(), MessageTypeDefOf.RejectInput, false); + return false; + } + + // We only want player to target world object when there's no colonist in the map + // Only if mark is needed + if (map != null && (!MandatoryMarkToFireOutBounds || map.mapPawns.AnyPawnBlockingMapRemoval)) { - GenDraw.DrawTargetHighlight(target); - }, targetValidator: (target) => + return AttackWorldTile(turrets, targetInfo, map); + } + + return AttackWorldObject(turrets, targetInfo); + }, + canTargetTiles: true, + closeWorldTabWhenFinished: true, + onUpdate: () => + { + if (others != null) { - RoofDef roof = map.roofGrid.RoofAt(target.Cell); - if ((roof == null || roof == RoofDefOf.RoofConstructed) && - target.Cell.GetFirstThing(map) != null) + foreach (var t in SelectedTurrets) { - return true; + int radius2 = Mathf.FloorToInt(t.MaxWorldRange); + if (radius2 != radius) + { + ShellingUtility.CachedDrawTurretRadiusRing(t.Tile, radius2, CanShootOtherLayers); + } } - else + } + ShellingUtility.CachedDrawTurretRadiusRing(turretTile, radius, CanShootOtherLayers); + }, + extraLabelGetter: (targetInfo) => + { + // Remove label when bad layer + if (PlanetLayer.Selected != targetInfo.Tile.Layer) + { + return ""; + } + int distanceToTarget = ShellingUtility.GetDistancePlanetTiles(turretTile, targetInfo.Tile); + float maxWorldRange = turret.MaxWorldRange; + string distanceMessage = null; + if (others != null) + { + int inRangeCount = 0; + int count = 0; + foreach (var t in SelectedTurrets) { - Messages.Message("CE_ArtilleryTarget_MustTargetMark".Translate(), MessageTypeDefOf.RejectInput); - return false; + count++; + if (t.MaxWorldRange >= distanceToTarget) + { + inRangeCount++; + } } - }); - return false; - } - if (targetInfo.WorldObject.Destroyed || targetInfo.WorldObject is DestroyedSettlement || targetInfo.WorldObject.def == WorldObjectDefOf.DestroyedSettlement) - { - Messages.Message("CE_ArtilleryTarget_AlreadyDestroyed".Translate(), MessageTypeDefOf.CautionInput); - return false; - } - if (targetInfo.WorldObject.Faction != null) - { - Faction targetFaction = targetInfo.WorldObject.Faction; - FactionRelation relation = targetFaction.RelationWith(turret.Faction, true); - if (relation == null) + distanceMessage = "CE_ArtilleryTarget_Distance_Selections".Translate().Formatted(distanceToTarget, inRangeCount, count); + } + else { - targetFaction.TryMakeInitialRelationsWith(turret.Faction); + distanceMessage = "CE_ArtilleryTarget_Distance".Translate().Formatted(distanceToTarget, maxWorldRange); } - if (!targetFaction.HostileTo(turret.Faction) && !targetFaction.Hidden) + if (maxWorldRange > 0 && distanceToTarget > maxWorldRange) { - Find.WindowStack.Add( - new Dialog_MessageBox( - "CE_ArtilleryTarget_AttackingAllies".Translate().Formatted(targetInfo.WorldObject.Label, targetFaction.Name), - "CE_Yes".Translate(), - delegate - { - TryAttack(turrets, targetInfo, LocalTargetInfo.Invalid); - Find.WorldTargeter.StopTargeting(); - }, - "CE_No".Translate(), - delegate - { - Find.WorldTargeter.StopTargeting(); - }, buttonADestructive: true)); - return false; + GUI.color = ColorLibrary.RedReadable; + return distanceMessage + "\n" + "CE_ArtilleryTarget_DestinationBeyondMaximumRange".Translate(); } - } - return TryAttack(turrets, targetInfo, LocalTargetInfo.Invalid); - }, true, closeWorldTabWhenFinished: true, onUpdate: () => - { - if (others != null) - { - foreach (var t in SelectedTurrets) + if (!targetInfo.HasWorldObject || targetInfo.WorldObject is Caravan) { - if (t.MaxWorldRange != radius) - { - GenDraw.DrawWorldRadiusRing(turretTile, (int)t.MaxWorldRange); - } + GUI.color = ColorLibrary.RedReadable; + return distanceMessage + "\n" + "CE_ArtilleryTarget_InvalidTarget".Translate(); } - } - GenDraw.DrawWorldRadiusRing(turretTile, radius); - }, extraLabelGetter: (targetInfo) => - { - int distanceToTarget = Find.WorldGrid.TraversalDistanceBetween(turretTile, targetInfo.Tile, true); - string distanceMessage = null; - if (others != null) - { - int inRangeCount = 0; - int count = 0; - foreach (var t in SelectedTurrets) + string extra = ""; + if (targetInfo.WorldObject is Settlement settlement) { - count++; - if (t.MaxWorldRange >= distanceToTarget) + extra = $" {settlement.Name}"; + if (settlement.Faction != null && !settlement.Faction.name.NullOrEmpty()) { - inRangeCount++; + extra += $" ({settlement.Faction.name})"; } } - distanceMessage = "CE_ArtilleryTarget_Distance_Selections".Translate().Formatted(distanceToTarget, inRangeCount, count); - } - else - { - distanceMessage = "CE_ArtilleryTarget_Distance".Translate().Formatted(distanceToTarget, radius); - } - if (turret.MaxWorldRange > 0 && distanceToTarget > turret.MaxWorldRange) - { - GUI.color = ColorLibrary.RedReadable; - return distanceMessage + "\n" + "CE_ArtilleryTarget_DestinationBeyondMaximumRange".Translate(); - } - if (!targetInfo.HasWorldObject || targetInfo.WorldObject is Caravan) - { - GUI.color = ColorLibrary.RedReadable; - return distanceMessage + "\n" + "CE_ArtilleryTarget_InvalidTarget".Translate(); - } - string extra = ""; - if (targetInfo.WorldObject is Settlement settlement) + return distanceMessage + "\n" + "CE_ArtilleryTarget_ClickToOrderAttack".Translate() + extra; + }, + canSelectTarget: (targetInfo) => { - extra = $" {settlement.Name}"; - if (settlement.Faction != null && !settlement.Faction.name.NullOrEmpty()) + if ( + // Is unvalid + !targetInfo.HasWorldObject + // Fire on its own tile + || targetInfo.Tile == turretTile + // World object has neither a Map nor a HealthComp (ennemy faction) + || (targetInfo.WorldObject as MapParent)?.Map == null && + targetInfo.WorldObject.GetComponent() == null) { - extra += $" ({settlement.Faction.name})"; + return false; } + return true; + }); + CommandProcessInput(ev); + } + + /// + /// Return false to stop the targeting process, true to continue. + /// + protected virtual bool AdditionnalTargettingCondition(GlobalTargetInfo targetInfo) + { + return true; + } + + protected bool TryAttack(IEnumerable turrets, GlobalTargetInfo targetInfo, LocalTargetInfo localTargetInfo) + { + bool attackStarted = false; + foreach (var t in turrets) + { + if (t.Active && t.TryAttackWorldTarget(targetInfo, localTargetInfo)) + { + attackStarted = attackStarted || true; } - return distanceMessage + "\n" + "CE_ArtilleryTarget_ClickToOrderAttack".Translate() + extra; - }, canSelectTarget: (targetInfo) => + } + return attackStarted; + } + + private bool AttackWorldTile(IEnumerable turrets, GlobalTargetInfo targetInfo, Map map) + { + IntVec3 selectedCell = IntVec3.Invalid; + Find.WorldTargeter.StopTargeting(); + CameraJumper.TryJumpInternal(new IntVec3((int)map.Size.x / 2, 0, (int)map.Size.z / 2), map, CameraJumper.MovementMode.Pan); + Find.Targeter.BeginTargeting(new TargetingParameters() { - if (!targetInfo.HasWorldObject || targetInfo.Tile == turretTile || - (targetInfo.WorldObject as MapParent)?.Map == null && - targetInfo.WorldObject.GetComponent() == null) + canTargetLocations = true, + canTargetBuildings = true, + canTargetHumans = true + }, (target) => + { + targetInfo.mapInt = map; + targetInfo.tileInt = map.Tile; + targetInfo.cellInt = target.cellInt; + //targetInfo.thingInt = target.thingInt; + TryAttack(turrets, targetInfo, target); + }, highlightAction: (target) => + { + GenDraw.DrawTargetHighlight(target); + }, targetValidator: (target) => + { + // Cannot fire through mountain roof + RoofDef roof = map.roofGrid.RoofAt(target.Cell); + if (roof != null && roof != RoofDefOf.RoofConstructed) + { + Messages.Message("CE_ArtilleryTarget_NoThickRoof".Translate(), MessageTypeDefOf.RejectInput); + return false; + } + + // Marker condition + if (MandatoryMarkToFireOutBounds && target.Cell.GetFirstThing(map) == null) { + Messages.Message("CE_ArtilleryTarget_MustTargetMark".Translate(), MessageTypeDefOf.RejectInput); return false; } + return true; }); - base.ProcessInput(ev); + return false; } - private bool TryAttack(IEnumerable turrets, GlobalTargetInfo targetInfo, LocalTargetInfo localTargetInfo) + private bool AttackWorldObject(IEnumerable turrets, GlobalTargetInfo targetInfo) { - bool attackStarted = false; - foreach (var t in turrets) + if (targetInfo.WorldObject.Destroyed || targetInfo.WorldObject is DestroyedSettlement || targetInfo.WorldObject.def == WorldObjectDefOf.DestroyedSettlement) { - if (t.Active && t.TryAttackWorldTarget(targetInfo, localTargetInfo)) + Messages.Message("CE_ArtilleryTarget_AlreadyDestroyed".Translate(), MessageTypeDefOf.CautionInput); + return false; + } + + if (targetInfo.WorldObject.Faction != null) + { + if (targetInfo.WorldObject.Faction == Faction.OfPlayer) { - attackStarted = attackStarted || true; + // We should not be able to target our own faction + return false; + } + + Faction targetFaction = targetInfo.WorldObject.Faction; + FactionRelation relation = targetFaction.RelationWith(turret.Faction, true); + if (relation == null) + { + targetFaction.TryMakeInitialRelationsWith(turret.Faction); + } + if (!targetFaction.HostileTo(turret.Faction) && !targetFaction.Hidden) + { + Find.WindowStack.Add( + new Dialog_MessageBox( + "CE_ArtilleryTarget_AttackingAllies".Translate().Formatted(targetInfo.WorldObject.Label, targetFaction.Name), + "CE_Yes".Translate(), + delegate + { + TryAttack(turrets, targetInfo, LocalTargetInfo.Invalid); + Find.WorldTargeter.StopTargeting(); + }, + "CE_No".Translate(), + delegate + { + Find.WorldTargeter.StopTargeting(); + }, buttonADestructive: true)); + return false; } } - return attackStarted; + return TryAttack(turrets, targetInfo, LocalTargetInfo.Invalid); } + #endregion } diff --git a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs index e8d917f056..33d5130861 100755 --- a/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Projectiles/ProjectileCE.cs @@ -1287,27 +1287,7 @@ public override void Tick() { if (globalTargetInfo.IsValid) { - TravelingShell shell = (TravelingShell)WorldObjectMaker.MakeWorldObject(CE_WorldObjectDefOf.TravelingShell); - if (launcher?.Faction != null) - { - shell.SetFaction(launcher.Faction); - } - shell.Tile = Map.Tile; - shell.SpawnSetup(); - Find.World.worldObjects.Add(shell); - shell.launcher = launcher; - shell.equipmentDef = equipmentDef; - shell.globalSource = new GlobalTargetInfo(OriginIV3, Map); - shell.globalSource.tileInt = Map.Tile; - shell.globalSource.mapInt = Map; - shell.globalSource.worldObjectInt = Map.Parent; - shell.shellDef = def; - shell.globalTarget = globalTargetInfo; - if (!shell.TryTravel(Map.Tile, globalTargetInfo.Tile)) - { - Log.Error($"CE: Travling shell {this.def} failed to launch!"); - shell.Destroy(); - } + CreateShellWorldObject(); } Destroy(); return; @@ -1364,6 +1344,36 @@ public override void Tick() } } + protected void CreateShellWorldObject() + { + TravelingShell shell = (TravelingShell)WorldObjectMaker.MakeWorldObject(CE_WorldObjectDefOf.TravelingShell); + if (launcher?.Faction != null) + { + shell.SetFaction(launcher.Faction); + } + shell.Tile = Map.Tile; + shell.SpawnSetup(); + Find.World.worldObjects.Add(shell); + shell.launcher = launcher; + shell.equipmentDef = equipmentDef; + shell.globalSource = new GlobalTargetInfo(OriginIV3, Map); + shell.globalSource.tileInt = Map.Tile; + shell.globalSource.mapInt = Map; + shell.globalSource.worldObjectInt = Map.Parent; + shell.shellDef = def; + shell.globalTarget = globalTargetInfo; + if (Props.shellingProps?.arrivedAtSameProps ?? false) + { + shell.arrivedShotHeight = shotHeight; + shell.arrivedShotSpeed = shotSpeed; + } + if (!shell.TryTravel(Map.Tile, globalTargetInfo.Tile)) + { + Log.Error($"CE: Travling shell {this.def} failed to launch!"); + shell.Destroy(); + } + } + /// /// Draws projectile if at least a tick away from caster (or always if no caster) /// diff --git a/Source/CombatExtended/CombatExtended/ShellingUtility.cs b/Source/CombatExtended/CombatExtended/ShellingUtility.cs index e22c294f6c..0f2c2a0b8d 100755 --- a/Source/CombatExtended/CombatExtended/ShellingUtility.cs +++ b/Source/CombatExtended/CombatExtended/ShellingUtility.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using RimWorld; using RimWorld.Planet; using UnityEngine; using Verse; -using CombatExtended.WorldObjects; namespace CombatExtended; public static class ShellingUtility @@ -16,6 +14,90 @@ public static class ShellingUtility private static ProjectilePropertiesCE props; private static DamageDef projectileDamageDef; + private struct DistanceCache + { + public PlanetTile startingTile; + public PlanetTile destinationTile; + public int distance; + } + private static DistanceCache distanceCache = new DistanceCache(); + + public static int GetDistancePlanetTiles(PlanetTile startingTile, PlanetTile destinationTile, int maxDist = int.MaxValue) + { + if (distanceCache.startingTile == startingTile && distanceCache.destinationTile == destinationTile) + { + return distanceCache.distance; + } + + distanceCache.startingTile = startingTile; + distanceCache.destinationTile = destinationTile; + + distanceCache.distance = + (int)(Find.WorldGrid.TraversalDistanceBetween(startingTile, destinationTile, true, maxDist, true) * destinationTile.LayerDef.rangeDistanceFactor); + + return distanceCache.distance; + } + + private struct RadiusCache + { + public PlanetTile realCenterTile; + public int radius; + } + private static Dictionary radiusCache = new Dictionary(); + + public static void ClearRadiusCache() + { + radiusCache.Clear(); + } + + public static void CachedDrawTurretRadiusRing(PlanetTile center, int radius, bool canShootOtherLayers = false) + { + PlanetTile realCenterTile; + int realRadius; + RadiusCache cache; + + // Try to find cache. + bool cacheFound = radiusCache.TryGetValue(center.tileId, out cache); + + // If the result is not on the current layer, we need to recalculate it + if (cacheFound && cache.realCenterTile.Layer != PlanetLayer.Selected) + { + if (!canShootOtherLayers) + { + // Don't display radius + return; + } + + cacheFound = false; + radiusCache.Remove(center.tileId); + } + + // Use cached values + if (cacheFound) + { + realCenterTile = cache.realCenterTile; + realRadius = cache.radius; + } + else + { + // We cache these operations, because there is no need to overcharge the update. + realCenterTile = center; + float rangeDistanceFactor = PlanetLayer.Selected.Def.rangeDistanceFactor; + if (center.Layer != PlanetLayer.Selected) + { + realCenterTile = PlanetLayer.Selected.GetClosestTile_NewTemp(center); + } + realRadius = Mathf.FloorToInt(radius / rangeDistanceFactor); + + // Add result to cache + radiusCache.Add(center.tileId, new RadiusCache() { + realCenterTile = realCenterTile, + radius = realRadius + }); + } + GenDraw.DrawWorldRadiusRing(realCenterTile, realRadius); + } + public static IntVec3 FindRandomImpactCell(Map map, ThingDef shellDef = null) { ShellingUtility.map = map; diff --git a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs index 6f5b00781e..86e9ad2ca8 100755 --- a/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs +++ b/Source/CombatExtended/CombatExtended/Things/Building_TurretGunCE.cs @@ -47,6 +47,7 @@ public class Building_TurretGunCE : Building_Turret private CompAmmoUser compAmmo = null; private CompFireModes compFireModes = null; private CompChangeableProjectile compChangeable = null; + private CompOrbitalTurret compOrbitalTurret = null; public bool isReloading = false; private int ticksUntilAutoReload = 0; private bool everSpawned = false; @@ -153,7 +154,20 @@ public CompFireModes CompFireModes return compFireModes; } } - private ProjectilePropertiesCE ProjectileProps => (ProjectilePropertiesCE)compAmmo?.CurAmmoProjectile?.projectile ?? null; + + public CompOrbitalTurret CompOrbitalTurret + { + get + { + if (compOrbitalTurret == null && Gun != null) + { + compOrbitalTurret = Gun.TryGetComp(); + } + return compOrbitalTurret; + } + } + + 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; @@ -672,7 +686,7 @@ public bool TryAttackWorldTarget(GlobalTargetInfo targetInfo, LocalTargetInfo lo { ResetCurrentTarget(); ResetForcedTarget(); - int distanceToTarget = Find.WorldGrid.TraversalDistanceBetween(Map.Tile, targetInfo.Tile, true, maxDist: (int)(this.MaxWorldRange * 1.5f)); + int distanceToTarget = ShellingUtility.GetDistancePlanetTiles(Map.Tile, targetInfo.Tile, (int)(MaxWorldRange * 1.5f)); if (distanceToTarget > MaxWorldRange) { return false; @@ -694,8 +708,8 @@ public bool TryAttackWorldTarget(GlobalTargetInfo targetInfo, LocalTargetInfo lo public virtual void TryOrderAttackWorldTile(GlobalTargetInfo targetInf, IntVec3? cell = null) { - int startingTile = Map.Tile; - int destinationTile = targetInf.Tile; + PlanetTile startingTile = Map.Tile; + PlanetTile destinationTile = targetInf.Tile; Vector3 direction = (Find.WorldGrid.GetTileCenter(startingTile) - Find.WorldGrid.GetTileCenter(destinationTile)).normalized; Vector3 shotPos = DrawPos.Yto0(); @@ -740,7 +754,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_LaunchProjectileCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs index 4081b39cee..30c2ee85df 100644 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_LaunchProjectileCE.cs @@ -704,7 +704,7 @@ public virtual ShiftVecReport ShiftVecReportFor(GlobalTargetInfo target) return null; } // multiplie by 250 to emulate cells - int distanceToTarget = Find.WorldGrid.TraversalDistanceBetween(target.Tile, caster.Map.Tile, true); + int distanceToTarget = Find.WorldGrid.TraversalDistanceBetween(target.Tile, caster.Map.Tile, true, int.MaxValue, true); LocalTargetInfo localTarget = new LocalTargetInfo(); localTarget.cellInt = target.Cell; diff --git a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs index f0433f3438..30132c6cca 100644 --- a/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs +++ b/Source/CombatExtended/CombatExtended/Verbs/Verb_ShootMortarCE.cs @@ -29,12 +29,12 @@ public class Verb_ShootMortarCE : Verb_ShootCE /// /// Wether the target is marked /// - private bool targetHasMarker = false; + protected bool targetHasMarker = false; // for global target only // - private int startingTile; - private int destinationTile; + private PlanetTile startingTile; + private PlanetTile destinationTile; private int globalDistance; private Vector3 direction; private new int numShotsFired; @@ -112,7 +112,20 @@ public override ShiftVecReport ShiftVecReportFor(GlobalTargetInfo target) return null; } ShiftVecReport report = base.ShiftVecReportFor(target); - report.circularMissRadius = GetGlobalMissRadiusForDist(report.shotDist); + + float shotDist = report.shotDist; + + // Shelling across layers + if (globalSourceInfo.Tile.Layer != globalTargetInfo.Tile.Layer) + { + CompOrbitalTurret compOrbitalTurret = caster.TryGetComp(); + if (compOrbitalTurret != null && compOrbitalTurret.Props.interLayerPrecisionBonusFactor != 0) + { + shotDist = shotDist / compOrbitalTurret.Props.interLayerPrecisionBonusFactor; + } + } + + report.circularMissRadius = GetGlobalMissRadiusForDist(shotDist); report.weatherShift = (1f - globalTargetInfo.Map.weatherManager.CurWeatherAccuracyMultiplier) * 1.5f + (1 - globalSourceInfo.Map.weatherManager.CurWeatherAccuracyMultiplier) * 0.5f; ArtilleryMarker marker = null; diff --git a/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShell.cs b/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShell.cs index 8f82c4d961..16ece7b221 100755 --- a/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShell.cs +++ b/Source/CombatExtended/CombatExtended/WorldObjects/TravelingShell.cs @@ -17,6 +17,8 @@ public class TravelingShell : TravelingThing public ThingDef equipmentDef; public ThingDef shellDef; public Thing launcher; + public float arrivedShotHeight = 200f; + public float arrivedShotSpeed = 55f; private Texture2D expandingIcon; public override Texture2D ExpandingIcon { @@ -39,6 +41,7 @@ public override float TilesPerTick { get => (shellDef.projectile as ProjectilePropertiesCE).shellingProps.tilesPerTick; } + public bool IsInstant => (shellDef.projectile as ProjectilePropertiesCE).isInstant; public override bool ExpandingIconFlipHorizontal { @@ -96,7 +99,7 @@ public override string GetDescription() protected override void Arrived() { int tile = Tile; - foreach (WorldObject worldObject in Find.World.worldObjects.ObjectsAt(tile)) + foreach (WorldObject worldObject in Find.World.worldObjects.ObjectsAt(Tile)) { if (TryShell(worldObject)) { @@ -125,11 +128,7 @@ private bool TryShell(WorldObject worldObject) 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); + LaunchProjectile(sourceCell, targetCell, map); } WorldObjects.HostilityComp hostility = worldObject.GetComponent(); WorldObjects.HealthComp healthComp = worldObject.GetComponent(); @@ -148,20 +147,20 @@ private bool TryShell(WorldObject worldObject) return shelled; } - private void LaunchProjectile(IntVec3 sourceCell, LocalTargetInfo target, Map map, float shotSpeed = 20, float shotHeight = 200) + private void LaunchProjectile(IntVec3 sourceCell, LocalTargetInfo target, Map map) { - Vector3 source = new Vector3(sourceCell.x, shotHeight, sourceCell.z); + Vector3 source = new Vector3(sourceCell.x, arrivedShotHeight, 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); + float shotAngle = pprops.TrajectoryWorker.ShotAngle(pprops, source, targetPos, arrivedShotSpeed); projectile.canTargetSelf = false; projectile.Position = sourceCell; projectile.SpawnSetup(map, false); - projectile.Launch(launcher, new Vector2(source.x, source.z), shotAngle, shotRotation, shotHeight, shotSpeed); + projectile.Launch(launcher, new Vector2(source.x, source.z), shotAngle, shotRotation, arrivedShotHeight, arrivedShotSpeed); //projectile.cameraShakingInit = Rand.Range(0f, 2f); } 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 { diff --git a/Source/CombatExtended/CombatExtended/WorldObjects/TravelingThing.cs b/Source/CombatExtended/CombatExtended/WorldObjects/TravelingThing.cs index 68aea2ae08..a748aec40d 100755 --- a/Source/CombatExtended/CombatExtended/WorldObjects/TravelingThing.cs +++ b/Source/CombatExtended/CombatExtended/WorldObjects/TravelingThing.cs @@ -8,8 +8,8 @@ namespace CombatExtended; public abstract class TravelingThing : WorldObject { - private int startingTile; - private int destinationTile; + private PlanetTile startingTile; + private PlanetTile destinationTile; private int distanceInTiles; private float tilesPerTick; @@ -31,13 +31,13 @@ public virtual float TilesPerTick { get => 0.03f; } - public int StartTile + public PlanetTile StartTile { get => startingTile; } - public int DestinationTile + public PlanetTile DestinationTile { - get => startingTile; + get => destinationTile; } public float TraveledPtc => this.distanceTraveled / this.distanceInTiles; @@ -47,22 +47,24 @@ public TravelingThing() { } - public virtual bool TryTravel(int startingTile, int destinationTile) + public virtual bool TryTravel(PlanetTile startingTile, PlanetTile destinationTile) { if (startingTile <= -1 || destinationTile <= -1 || startingTile == destinationTile) { Log.Warning($"CE: TryTravel in thing {this} got {startingTile} {destinationTile}"); return false; } + this.startingTile = this.Tile = startingTile; this.destinationTile = destinationTile; this.tilesPerTick = TilesPerTick; - Vector3 start = Find.WorldGrid.GetTileCenter(startingTile); - Vector3 end = Find.WorldGrid.GetTileCenter(destinationTile); - - this.distance = GenMath.SphericalDistance(start.normalized, end.normalized); - this.distanceInTiles = (int)Find.World.grid.ApproxDistanceInTiles(this.distance); + PlanetLayer layerToUse = Find.WorldGrid.PlanetLayers.TryGetValue(0); // gound layer + if (startingTile.Layer == destinationTile.Layer) + { + layerToUse = startingTile.Layer; + } + this.distanceInTiles = (int)layerToUse.ApproxDistanceInTiles(startingTile, destinationTile); return true; } diff --git a/Source/MiscTurretsCompat/MiscTurretsCompat.csproj b/Source/MiscTurretsCompat/MiscTurretsCompat.csproj index 377d15e481..011a1dc66a 100644 --- a/Source/MiscTurretsCompat/MiscTurretsCompat.csproj +++ b/Source/MiscTurretsCompat/MiscTurretsCompat.csproj @@ -48,7 +48,7 @@ - + diff --git a/Source/MultiplayerCompat/MultiplayerCompat.csproj b/Source/MultiplayerCompat/MultiplayerCompat.csproj index 30617b2d77..de4f91e545 100644 --- a/Source/MultiplayerCompat/MultiplayerCompat.csproj +++ b/Source/MultiplayerCompat/MultiplayerCompat.csproj @@ -40,7 +40,7 @@ - + diff --git a/Source/PsyblastersCompat/PsyblastersCompat.csproj b/Source/PsyblastersCompat/PsyblastersCompat.csproj index c92dd2ea12..d039aca777 100644 --- a/Source/PsyblastersCompat/PsyblastersCompat.csproj +++ b/Source/PsyblastersCompat/PsyblastersCompat.csproj @@ -41,7 +41,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Source/SOS2Compat/SOS2Compat.csproj b/Source/SOS2Compat/SOS2Compat.csproj index f42b714d23..487ceadbae 100644 --- a/Source/SOS2Compat/SOS2Compat.csproj +++ b/Source/SOS2Compat/SOS2Compat.csproj @@ -61,7 +61,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Source/SRTSCompat/SRTSCompat.csproj b/Source/SRTSCompat/SRTSCompat.csproj index bc5a3457a5..c441ec6c87 100644 --- a/Source/SRTSCompat/SRTSCompat.csproj +++ b/Source/SRTSCompat/SRTSCompat.csproj @@ -46,7 +46,7 @@ - + diff --git a/Source/VFESecurityCompat/VFESecurityCompat.csproj b/Source/VFESecurityCompat/VFESecurityCompat.csproj index 57deaa1738..0cdfbe80ac 100644 --- a/Source/VFESecurityCompat/VFESecurityCompat.csproj +++ b/Source/VFESecurityCompat/VFESecurityCompat.csproj @@ -44,7 +44,7 @@ - + diff --git a/Source/VehiclesCompat/VehiclesCompat.csproj b/Source/VehiclesCompat/VehiclesCompat.csproj index c1749e127b..60448d09fd 100644 --- a/Source/VehiclesCompat/VehiclesCompat.csproj +++ b/Source/VehiclesCompat/VehiclesCompat.csproj @@ -55,7 +55,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - +