diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 3a4b5d3602..848dc1b54c 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -241,6 +241,7 @@ namespace OpenRA bool initializedCellProjection; CellLayer cellProjection; CellLayer> inverseCellProjection; + CellLayer projectedHeight; public static string ComputeUID(IReadOnlyPackage package) { @@ -420,6 +421,7 @@ namespace OpenRA cellProjection = new CellLayer(this); inverseCellProjection = new CellLayer>(this); + projectedHeight = new CellLayer(this); // Initialize collections foreach (var cell in AllCells) @@ -455,13 +457,54 @@ namespace OpenRA // Remove old reverse projection foreach (var puv in cellProjection[uv]) - inverseCellProjection[(MPos)puv].Remove(uv); + { + var temp = (MPos)puv; + inverseCellProjection[temp].Remove(uv); + projectedHeight[temp] = ProjectedCellHeightInner(puv); + } var projected = ProjectCellInner(uv); cellProjection[uv] = projected; foreach (var puv in projected) - inverseCellProjection[(MPos)puv].Add(uv); + { + var temp = (MPos)puv; + inverseCellProjection[temp].Add(uv); + + var height = ProjectedCellHeightInner(puv); + projectedHeight[temp] = height; + + // Propagate height up cliff faces + while (true) + { + temp = new MPos(temp.U, temp.V - 1); + if (!inverseCellProjection.Contains(temp) || inverseCellProjection[temp].Any()) + break; + + projectedHeight[temp] = height; + } + } + } + + byte ProjectedCellHeightInner(PPos puv) + { + while (inverseCellProjection.Contains((MPos)puv)) + { + var inverse = inverseCellProjection[(MPos)puv]; + if (inverse.Any()) + { + // The original games treat the top of cliffs the same way as the bottom + // This information isn't stored in the map data, so query the offset from the tileset + var temp = inverse.MaxBy(uv => uv.V); + var terrain = Tiles[temp]; + return (byte)(Height[temp] - Rules.TileSet.Templates[terrain.Type][terrain.Index].Height); + } + + // Try the next cell down if this is a cliff face + puv = new PPos(puv.U, puv.V + 1); + } + + return 0; } PPos[] ProjectCellInner(MPos uv) @@ -786,6 +829,11 @@ namespace OpenRA return inverseCellProjection[uv]; } + public byte ProjectedHeight(PPos puv) + { + return projectedHeight[(MPos)puv]; + } + public int FacingBetween(CPos cell, CPos towards, int fallbackfacing) { var delta = CenterOfCell(towards) - CenterOfCell(cell); diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index aeece72891..5a26727b46 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -115,7 +115,7 @@ namespace OpenRA.Traits Hash += 1; } - public static IEnumerable ProjectedCellsInRange(Map map, WPos pos, WDist range) + public static IEnumerable ProjectedCellsInRange(Map map, WPos pos, WDist range, int maxHeightDelta = -1) { // Account for potential extra half-cell from odd-height terrain var r = (range.Length + 1023 + 512) / 1024; @@ -124,15 +124,22 @@ namespace OpenRA.Traits // Project actor position into the shroud plane var projectedPos = pos - new WVec(0, pos.Z, pos.Z); var projectedCell = map.CellContaining(projectedPos); + var projectedHeight = pos.Z / 512; foreach (var c in map.FindTilesInCircle(projectedCell, r, true)) + { if ((map.CenterOfCell(c) - projectedPos).HorizontalLengthSquared <= limit) - yield return (PPos)c.ToMPos(map); + { + var puv = (PPos)c.ToMPos(map); + if (maxHeightDelta < 0 || map.ProjectedHeight(puv) < projectedHeight + maxHeightDelta) + yield return puv; + } + } } - public static IEnumerable ProjectedCellsInRange(Map map, CPos cell, WDist range) + public static IEnumerable ProjectedCellsInRange(Map map, CPos cell, WDist range, int maxHeightDelta = -1) { - return ProjectedCellsInRange(map, map.CenterOfCell(cell), range); + return ProjectedCellsInRange(map, map.CenterOfCell(cell), range, maxHeightDelta); } public void AddProjectedVisibility(object key, PPos[] visible) diff --git a/OpenRA.Mods.Common/Traits/AffectsShroud.cs b/OpenRA.Mods.Common/Traits/AffectsShroud.cs index 357b01a7e8..8e8fd93966 100644 --- a/OpenRA.Mods.Common/Traits/AffectsShroud.cs +++ b/OpenRA.Mods.Common/Traits/AffectsShroud.cs @@ -18,6 +18,9 @@ namespace OpenRA.Mods.Common.Traits { public readonly WDist Range = WDist.Zero; + [Desc("If >= 0, prevent cells that are this much higher than the actor from being revealed.")] + public readonly int MaxHeightDelta = -1; + [Desc("Possible values are CenterPosition (measure range from the center) and ", "Footprint (measure range from the footprint)")] public readonly VisibilityType Type = VisibilityType.Footprint; @@ -47,10 +50,10 @@ namespace OpenRA.Mods.Common.Traits if (Info.Type == VisibilityType.Footprint) return self.OccupiesSpace.OccupiedCells() - .SelectMany(kv => Shroud.ProjectedCellsInRange(map, kv.First, range)) + .SelectMany(kv => Shroud.ProjectedCellsInRange(map, kv.First, range, Info.MaxHeightDelta)) .Distinct().ToArray(); - return Shroud.ProjectedCellsInRange(map, self.CenterPosition, range) + return Shroud.ProjectedCellsInRange(map, self.CenterPosition, range, Info.MaxHeightDelta) .ToArray(); } diff --git a/mods/ts/rules/civilian-structures.yaml b/mods/ts/rules/civilian-structures.yaml index 06cb66e260..5e73693476 100644 --- a/mods/ts/rules/civilian-structures.yaml +++ b/mods/ts/rules/civilian-structures.yaml @@ -1240,6 +1240,7 @@ GASPOT: HP: 400 RevealsShroud: Range: 6c0 + MaxHeightDelta: 3 WithIdleOverlay@LIGHTS: Sequence: idle-lights SelectionDecorations: @@ -1261,6 +1262,7 @@ GALITE: Type: Wood RevealsShroud: Range: 2c0 + MaxHeightDelta: 3 Power: Amount: 0 # TODO: should use terrain lighting instead, depends on https://github.com/OpenRA/OpenRA/issues/3605 @@ -1298,6 +1300,7 @@ GAICBM: Type: Wood RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Transforms: IntoActor: icbm Offset: 1,1 diff --git a/mods/ts/rules/civilian-vehicles.yaml b/mods/ts/rules/civilian-vehicles.yaml index c918bf7478..75c53f476c 100644 --- a/mods/ts/rules/civilian-vehicles.yaml +++ b/mods/ts/rules/civilian-vehicles.yaml @@ -14,6 +14,7 @@ Type: Heavy RevealsShroud: Range: 6c0 + MaxHeightDelta: 3 Turreted: TurnSpeed: 3 Armament@PRIMARY: @@ -48,6 +49,7 @@ Speed: 56 RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 TRUCKA: Inherits: ^TRUCK @@ -70,6 +72,7 @@ ICBM: TurnSpeed: 5 RevealsShroud: Range: 7c0 + MaxHeightDelta: 3 Transforms: IntoActor: gaicbm Offset: -1,-1 @@ -93,6 +96,7 @@ BUS: Type: Light RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Cargo: Types: Infantry MaxWeight: 20 @@ -116,6 +120,7 @@ PICK: Type: Light RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Cargo: Types: Infantry MaxWeight: 2 @@ -139,6 +144,7 @@ CAR: Type: Light RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Cargo: Types: Infantry MaxWeight: 4 @@ -162,6 +168,7 @@ WINI: Type: Light RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Cargo: Types: Infantry MaxWeight: 5 diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index 3c4feccb5e..1faf7dce35 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -157,6 +157,7 @@ Type: Wood RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 Tooltip: RenderSprites: Palette: terraindecoration @@ -254,6 +255,7 @@ Cost: 10 RevealsShroud: Range: 2c0 + MaxHeightDelta: 3 Mobile: Voice: Move Speed: 71 @@ -794,6 +796,7 @@ Type: Light RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Selectable: Bounds: 40,24 WithTextControlGroupDecoration: diff --git a/mods/ts/rules/gdi-structures.yaml b/mods/ts/rules/gdi-structures.yaml index 769abf58f2..3c0a1733d7 100644 --- a/mods/ts/rules/gdi-structures.yaml +++ b/mods/ts/rules/gdi-structures.yaml @@ -20,6 +20,7 @@ GAPOWR: Type: Wood RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 WithIdleOverlay@LIGHTS: Sequence: idle-lights WithIdleOverlay@PLUG: @@ -87,6 +88,7 @@ GAPILE: Type: Wood RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 RallyPoint: Offset: 2,3 Palette: mouse @@ -141,6 +143,7 @@ GAWEAP: HP: 1000 RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 Armor: Type: Heavy RallyPoint: @@ -199,6 +202,7 @@ GAHPAD: HP: 600 RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Exit@1: SpawnOffset: 0,-256,0 ExitsDebugOverlay: @@ -255,6 +259,7 @@ GADEPT: HP: 1100 RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Reservable: RepairsUnits: PlayerExperience: 15 @@ -319,6 +324,7 @@ GARADR: RenderDetectionCircle: RevealsShroud: Range: 10c0 + MaxHeightDelta: 3 WithIdleOverlay@DISH: Sequence: idle-dish PauseOnLowPower: yes @@ -354,6 +360,7 @@ GATECH: Type: Wood RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 WithIdleOverlay@LIGHTS: Sequence: idle-lights Power: @@ -396,6 +403,7 @@ GAPLUG: Type: Wood RevealsShroud: Range: 6c0 + MaxHeightDelta: 3 IonCannonPower: Cursor: ioncannon UpgradeTypes: plug.ioncannon diff --git a/mods/ts/rules/gdi-vehicles.yaml b/mods/ts/rules/gdi-vehicles.yaml index 194e9e707c..db6bd3da65 100644 --- a/mods/ts/rules/gdi-vehicles.yaml +++ b/mods/ts/rules/gdi-vehicles.yaml @@ -20,6 +20,7 @@ APC: Type: Heavy RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Turreted: TurnSpeed: 10 Cargo: @@ -76,6 +77,7 @@ HVR: Type: Wood RevealsShroud: Range: 7c0 + MaxHeightDelta: 3 Armament: Weapon: HoverMissile LocalOffset: 0,171,384, 0,-171,384 @@ -118,6 +120,7 @@ SMECH: Type: Light RevealsShroud: Range: 6c0 + MaxHeightDelta: 3 AttackFrontal: Voice: Attack AutoTarget: @@ -160,6 +163,7 @@ MMCH: Type: Heavy RevealsShroud: Range: 8c0 + MaxHeightDelta: 3 BodyOrientation: QuantizedFacings: 32 UseClassicPerspectiveFudge: False @@ -209,6 +213,7 @@ HMEC: Type: Heavy RevealsShroud: Range: 8c0 + MaxHeightDelta: 3 AttackFrontal: Voice: Attack AutoTarget: @@ -249,6 +254,7 @@ SONIC: Type: Heavy RevealsShroud: Range: 7c0 + MaxHeightDelta: 3 Armament: Weapon: SonicZap LocalOffset: -50,0,410 diff --git a/mods/ts/rules/nod-structures.yaml b/mods/ts/rules/nod-structures.yaml index 8766c551c5..5cef90a257 100644 --- a/mods/ts/rules/nod-structures.yaml +++ b/mods/ts/rules/nod-structures.yaml @@ -22,6 +22,7 @@ NAPOWR: Type: Wood RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 WithIdleOverlay@LIGHTS: Sequence: idle-lights Power: @@ -60,6 +61,7 @@ NAAPWR: Type: Wood RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 WithIdleOverlay@LIGHTS: Sequence: idle-lights Power: @@ -98,6 +100,7 @@ NAHAND: Type: Wood RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Exit@1: SpawnOffset: 384,768,0 ExitCell: 3,2 @@ -152,6 +155,7 @@ NAWEAP: Type: Heavy RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 RallyPoint: Offset: 4,1 Palette: mouse @@ -204,6 +208,7 @@ NAHPAD: HP: 600 RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Exit@1: SpawnOffset: 0,-256,0 ExitsDebugOverlay: @@ -274,6 +279,7 @@ NARADR: RenderDetectionCircle: RevealsShroud: Range: 10c0 + MaxHeightDelta: 3 WithIdleOverlay@DISH: Sequence: idle-dish PauseOnLowPower: true @@ -309,6 +315,7 @@ NATECH: Type: Wood RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 WithIdleOverlay@LIGHTS: Sequence: idle-lights Power: @@ -341,6 +348,7 @@ NATMPL: Type: Wood RevealsShroud: Range: 6c0 + MaxHeightDelta: 3 Power: Amount: -200 WithIdleOverlay@LIGHTS: @@ -377,6 +385,7 @@ NASTLH: Type: Wood RevealsShroud: Range: 6c0 + MaxHeightDelta: 3 WithIdleOverlay@pulse: Sequence: pulse PauseOnLowPower: true @@ -422,6 +431,7 @@ NAWAST: HP: 400 RevealsShroud: Range: 6c0 + MaxHeightDelta: 3 TiberianSunRefinery: DockAngle: 160 DockOffset: 2,1 diff --git a/mods/ts/rules/nod-vehicles.yaml b/mods/ts/rules/nod-vehicles.yaml index 5252d51d1d..8efed6ee36 100644 --- a/mods/ts/rules/nod-vehicles.yaml +++ b/mods/ts/rules/nod-vehicles.yaml @@ -18,6 +18,7 @@ BGGY: Type: Light RevealsShroud: Range: 6c0 + MaxHeightDelta: 3 Armament: Weapon: RaiderCannon LocalOffset: 0,-43,384, 0,43,384 @@ -51,6 +52,7 @@ BIKE: Type: Wood RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Armament@PRIMARY: Weapon: BikeMissile UpgradeTypes: eliteweapon @@ -109,6 +111,7 @@ TTNK: WithMuzzleOverlay: RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 RenderSprites: Image: ttnk DeployToUpgrade: @@ -192,6 +195,7 @@ ART2: TurnSpeed: 2 RevealsShroud: Range: 9c0 + MaxHeightDelta: 3 RenderVoxels: LightAmbientColor: 0.4, 0.4, 0.4 Transforms: @@ -219,6 +223,7 @@ REPAIR: TurnSpeed: 5 RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Armament: Weapon: Repair Cursor: repair @@ -262,6 +267,7 @@ WEED: Type: Heavy RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 -WithVoxelBody: WithVoxelUnloadBody: -GainsExperience: @@ -289,6 +295,7 @@ SAPC: Type: Heavy RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Cargo: Types: Infantry MaxWeight: 5 @@ -318,6 +325,7 @@ SUBTANK: Type: Light RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Armament: Weapon: FireballLauncher AttackFrontal: @@ -345,6 +353,7 @@ STNK: Type: Light RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Cloak: InitialDelay: 90 CloakDelay: 90 diff --git a/mods/ts/rules/shared-structures.yaml b/mods/ts/rules/shared-structures.yaml index 98ef9c8120..75feddaa7a 100644 --- a/mods/ts/rules/shared-structures.yaml +++ b/mods/ts/rules/shared-structures.yaml @@ -15,6 +15,7 @@ GACNST: Type: Wood RevealsShroud: Range: 5c0 + MaxHeightDelta: 3 Production: Produces: Building,Defense Valued: @@ -73,6 +74,7 @@ PROC: HP: 900 RevealsShroud: Range: 6c0 + MaxHeightDelta: 3 TiberianSunRefinery: DockAngle: 160 DockOffset: 2,1 @@ -129,6 +131,7 @@ GASILO: Type: Wood RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 RenderSprites: Image: gasilo.gdi FactionImages: diff --git a/mods/ts/rules/shared-vehicles.yaml b/mods/ts/rules/shared-vehicles.yaml index 1d31650558..72f62fd590 100644 --- a/mods/ts/rules/shared-vehicles.yaml +++ b/mods/ts/rules/shared-vehicles.yaml @@ -19,6 +19,7 @@ MCV: Speed: 85 RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 MustBeDestroyed: RequiredForShortGame: true BaseBuilding: @@ -82,6 +83,7 @@ HARV: Type: Heavy RevealsShroud: Range: 4c0 + MaxHeightDelta: 3 -GainsExperience: -WithVoxelBody: WithVoxelUnloadBody: @@ -129,6 +131,7 @@ LPST: TurnSpeed: 5 RevealsShroud: Range: 7c0 + MaxHeightDelta: 3 RenderSprites: Image: lpst.gdi FactionImages: