diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 6dfd4f87ab..522297ce8c 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -900,6 +900,14 @@ namespace OpenRA return new WDist(offset.Z); } + public WRot TerrainOrientation(CPos cell) + { + if (!Ramp.Contains(cell)) + return WRot.None; + + return Grid.Ramps[Ramp[cell]].Orientation; + } + public WVec Offset(CVec delta, int dz) { if (Grid.Type == MapGridType.Rectangular) diff --git a/OpenRA.Game/Map/MapGrid.cs b/OpenRA.Game/Map/MapGrid.cs index f9330be8e1..113771b77c 100644 --- a/OpenRA.Game/Map/MapGrid.cs +++ b/OpenRA.Game/Map/MapGrid.cs @@ -27,9 +27,11 @@ namespace OpenRA public readonly int CenterHeightOffset; public readonly WVec[] Corners; public readonly WVec[][] Polygons; + public readonly WRot Orientation; - public CellRamp(MapGridType type, RampCornerHeight tl = RampCornerHeight.Low, RampCornerHeight tr = RampCornerHeight.Low, RampCornerHeight br = RampCornerHeight.Low, RampCornerHeight bl = RampCornerHeight.Low, RampSplit split = RampSplit.Flat) + public CellRamp(MapGridType type, WRot orientation, RampCornerHeight tl = RampCornerHeight.Low, RampCornerHeight tr = RampCornerHeight.Low, RampCornerHeight br = RampCornerHeight.Low, RampCornerHeight bl = RampCornerHeight.Low, RampSplit split = RampSplit.Flat) { + Orientation = orientation; if (type == MapGridType.RectangularIsometric) { Corners = new[] @@ -138,41 +140,52 @@ namespace OpenRA throw new InvalidDataException("Subcell default index must be a valid index into the offset triples and must be greater than 0 for mods with subcells"); } + // Rotation axes and amounts for the different slope types + var southEast = new WVec(724, 724, 0); + var southWest = new WVec(-724, 724, 0); + var south = new WVec(0, 1024, 0); + var east = new WVec(1024, 0, 0); + + var forward = new WAngle(64); + var backward = -forward; + var halfForward = new WAngle(48); + var halfBackward = -halfForward; + // Slope types are hardcoded following the convention from the TS and RA2 map format Ramps = new[] { // Flat - new CellRamp(Type), + new CellRamp(Type, WRot.None), // Two adjacent corners raised by half a cell - new CellRamp(Type, tr: RampCornerHeight.Half, br: RampCornerHeight.Half), - new CellRamp(Type, br: RampCornerHeight.Half, bl: RampCornerHeight.Half), - new CellRamp(Type, tl: RampCornerHeight.Half, bl: RampCornerHeight.Half), - new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Half), + new CellRamp(Type, new WRot(southEast, backward), tr: RampCornerHeight.Half, br: RampCornerHeight.Half), + new CellRamp(Type, new WRot(southWest, backward), br: RampCornerHeight.Half, bl: RampCornerHeight.Half), + new CellRamp(Type, new WRot(southEast, forward), tl: RampCornerHeight.Half, bl: RampCornerHeight.Half), + new CellRamp(Type, new WRot(southWest, forward), tl: RampCornerHeight.Half, tr: RampCornerHeight.Half), // One corner raised by half a cell - new CellRamp(Type, br: RampCornerHeight.Half, split: RampSplit.X), - new CellRamp(Type, bl: RampCornerHeight.Half, split: RampSplit.Y), - new CellRamp(Type, tl: RampCornerHeight.Half, split: RampSplit.X), - new CellRamp(Type, tr: RampCornerHeight.Half, split: RampSplit.Y), + new CellRamp(Type, new WRot(south, halfBackward), br: RampCornerHeight.Half, split: RampSplit.X), + new CellRamp(Type, new WRot(east, halfForward), bl: RampCornerHeight.Half, split: RampSplit.Y), + new CellRamp(Type, new WRot(south, halfForward), tl: RampCornerHeight.Half, split: RampSplit.X), + new CellRamp(Type, new WRot(east, halfBackward), tr: RampCornerHeight.Half, split: RampSplit.Y), // Three corners raised by half a cell - new CellRamp(Type, tr: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X), - new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.Y), - new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X), - new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.Y), + new CellRamp(Type, new WRot(south, halfBackward), tr: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X), + new CellRamp(Type, new WRot(east, halfForward), tl: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.Y), + new CellRamp(Type, new WRot(south, halfForward), tl: RampCornerHeight.Half, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X), + new CellRamp(Type, new WRot(east, halfBackward), tl: RampCornerHeight.Half, tr: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.Y), // Full tile sloped (mid corners raised by half cell, far corner by full cell) - new CellRamp(Type, tr: RampCornerHeight.Half, br: RampCornerHeight.Full, bl: RampCornerHeight.Half), - new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Full), - new CellRamp(Type, tl: RampCornerHeight.Full, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half), - new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Full, br: RampCornerHeight.Half), + new CellRamp(Type, new WRot(south, backward), tr: RampCornerHeight.Half, br: RampCornerHeight.Full, bl: RampCornerHeight.Half), + new CellRamp(Type, new WRot(east, forward), tl: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Full), + new CellRamp(Type, new WRot(south, forward), tl: RampCornerHeight.Full, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half), + new CellRamp(Type, new WRot(east, backward), tl: RampCornerHeight.Half, tr: RampCornerHeight.Full, br: RampCornerHeight.Half), // Two opposite corners raised by half a cell - new CellRamp(Type, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.Y), - new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.Y), - new CellRamp(Type, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X), - new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.X), + new CellRamp(Type, WRot.None, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.Y), + new CellRamp(Type, WRot.None, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.Y), + new CellRamp(Type, WRot.None, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X), + new CellRamp(Type, WRot.None, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.X), }; TilesByDistance = CreateTilesByDistance(); diff --git a/OpenRA.Mods.Common/Activities/Move/Move.cs b/OpenRA.Mods.Common/Activities/Move/Move.cs index fc5681efbe..0bf02c8e1c 100644 --- a/OpenRA.Mods.Common/Activities/Move/Move.cs +++ b/OpenRA.Mods.Common/Activities/Move/Move.cs @@ -221,7 +221,12 @@ namespace OpenRA.Mods.Common.Activities var to = Util.BetweenCells(self.World, mobile.FromCell, mobile.ToCell) + (map.Grid.OffsetOfSubCell(mobile.FromSubCell) + map.Grid.OffsetOfSubCell(mobile.ToSubCell)) / 2; - QueueChild(new MoveFirstHalf(this, from, to, mobile.Facing, mobile.Facing, carryoverProgress)); + WRot? toTerrainOrientation = null; + var margin = mobile.Info.TerrainOrientationAdjustmentMargin.Length; + if (margin >= 0) + toTerrainOrientation = WRot.SLerp(map.TerrainOrientation(mobile.FromCell), map.TerrainOrientation(mobile.ToCell), 1, 2); + + QueueChild(new MoveFirstHalf(this, from, to, mobile.Facing, mobile.Facing, null, toTerrainOrientation, margin, carryoverProgress)); carryoverProgress = 0; return false; } @@ -389,6 +394,7 @@ namespace OpenRA.Mods.Common.Activities protected readonly Move Move; protected readonly WPos From, To; protected readonly WAngle FromFacing, ToFacing; + protected readonly WRot? FromTerrainOrientation, ToTerrainOrientation; protected readonly bool EnableArc; protected readonly WPos ArcCenter; protected readonly int ArcFromLength; @@ -396,17 +402,22 @@ namespace OpenRA.Mods.Common.Activities protected readonly int ArcToLength; protected readonly WAngle ArcToAngle; protected readonly int Distance; + readonly int terrainOrientationMargin; protected int progress; - public MovePart(Move move, WPos from, WPos to, WAngle fromFacing, WAngle toFacing, int carryoverProgress) + public MovePart(Move move, WPos from, WPos to, WAngle fromFacing, WAngle toFacing, + WRot? fromTerrainOrientation, WRot? toTerrainOrientation, int terrainOrientationMargin, int carryoverProgress) { Move = move; From = from; To = to; FromFacing = fromFacing; ToFacing = toFacing; + FromTerrainOrientation = fromTerrainOrientation; + ToTerrainOrientation = toTerrainOrientation; progress = carryoverProgress; Distance = (to - from).Length; + this.terrainOrientationMargin = Math.Min(terrainOrientationMargin, Distance / 2); IsInterruptible = false; // See comments in Move.Cancel() @@ -471,6 +482,21 @@ namespace OpenRA.Mods.Common.Activities pos -= new WVec(WDist.Zero, WDist.Zero, self.World.Map.DistanceAboveTerrain(pos)); mobile.SetCenterPosition(self, pos); + + // Smoothly interpolate over terrain orientation changes + if (FromTerrainOrientation.HasValue && progress < terrainOrientationMargin) + { + var currentCellOrientation = self.World.Map.TerrainOrientation(mobile.FromCell); + var orientation = WRot.SLerp(FromTerrainOrientation.Value, currentCellOrientation, progress, terrainOrientationMargin); + mobile.SetTerrainRampOrientation(self, orientation); + } + else if (ToTerrainOrientation.HasValue && Distance - progress < terrainOrientationMargin) + { + var currentCellOrientation = self.World.Map.TerrainOrientation(mobile.FromCell); + var orientation = WRot.SLerp(ToTerrainOrientation.Value, currentCellOrientation, Distance - progress, terrainOrientationMargin); + mobile.SetTerrainRampOrientation(self, orientation); + } + mobile.Facing = WAngle.Lerp(FromFacing, ToFacing, progress, Distance); return false; } @@ -485,8 +511,9 @@ namespace OpenRA.Mods.Common.Activities class MoveFirstHalf : MovePart { - public MoveFirstHalf(Move move, WPos from, WPos to, WAngle fromFacing, WAngle toFacing, int carryoverProgress) - : base(move, from, to, fromFacing, toFacing, carryoverProgress) { } + public MoveFirstHalf(Move move, WPos from, WPos to, WAngle fromFacing, WAngle toFacing, + WRot? fromTerrainOrientation, WRot? toTerrainOrientation, int terrainOrientationMargin, int carryoverProgress) + : base(move, from, to, fromFacing, toFacing, fromTerrainOrientation, toTerrainOrientation, terrainOrientationMargin, carryoverProgress) { } static bool IsTurn(Mobile mobile, CPos nextCell, Map map) { @@ -513,12 +540,20 @@ namespace OpenRA.Mods.Common.Activities if (!mobile.IsTraitPaused && !mobile.IsTraitDisabled && IsTurn(mobile, nextCell.Value.Cell, map)) { var nextSubcellOffset = map.Grid.OffsetOfSubCell(nextCell.Value.SubCell); + WRot? nextToTerrainOrientation = null; + var margin = mobile.Info.TerrainOrientationAdjustmentMargin.Length; + if (margin >= 0) + nextToTerrainOrientation = WRot.SLerp(map.TerrainOrientation(mobile.ToCell), map.TerrainOrientation(nextCell.Value.Cell), 1, 2); + var ret = new MoveFirstHalf( Move, Util.BetweenCells(self.World, mobile.FromCell, mobile.ToCell) + (fromSubcellOffset + toSubcellOffset) / 2, Util.BetweenCells(self.World, mobile.ToCell, nextCell.Value.Cell) + (toSubcellOffset + nextSubcellOffset) / 2, mobile.Facing, map.FacingBetween(mobile.ToCell, nextCell.Value.Cell, mobile.Facing), + ToTerrainOrientation, + nextToTerrainOrientation, + margin, progress - Distance); mobile.FinishedMoving(self); @@ -538,6 +573,9 @@ namespace OpenRA.Mods.Common.Activities toPos + toSubcellOffset, mobile.Facing, mobile.Facing, + ToTerrainOrientation, + null, + mobile.Info.TerrainOrientationAdjustmentMargin.Length, progress - Distance); mobile.EnteringCell(self); @@ -548,8 +586,9 @@ namespace OpenRA.Mods.Common.Activities class MoveSecondHalf : MovePart { - public MoveSecondHalf(Move move, WPos from, WPos to, WAngle fromFacing, WAngle toFacing, int carryoverProgress) - : base(move, from, to, fromFacing, toFacing, carryoverProgress) { } + public MoveSecondHalf(Move move, WPos from, WPos to, WAngle fromFacing, WAngle toFacing, + WRot? fromTerrainOrientation, WRot? toTerrainOrientation, int terrainOrientationMargin, int carryoverProgress) + : base(move, from, to, fromFacing, toFacing, fromTerrainOrientation, toTerrainOrientation, terrainOrientationMargin, carryoverProgress) { } protected override MovePart OnComplete(Actor self, Mobile mobile, Move parent) { diff --git a/OpenRA.Mods.Common/Graphics/ModelRenderable.cs b/OpenRA.Mods.Common/Graphics/ModelRenderable.cs index 11e2b88490..b6614abb8d 100644 --- a/OpenRA.Mods.Common/Graphics/ModelRenderable.cs +++ b/OpenRA.Mods.Common/Graphics/ModelRenderable.cs @@ -133,8 +133,12 @@ namespace OpenRA.Mods.Common.Graphics this.model = model; var draw = model.models.Where(v => v.IsVisible); + var map = wr.World.Map; + var groundOrientation = map.TerrainOrientation(map.CellContaining(model.pos)); + var groundNormal = OpenRA.Graphics.Util.MatrixVectorMultiply(OpenRA.Graphics.Util.MakeFloatMatrix(groundOrientation.AsMatrix()), GroundNormal); + renderProxy = Game.Renderer.WorldModelRenderer.RenderAsync( - wr, draw, model.camera, model.scale, GroundNormal, model.lightSource, + wr, draw, model.camera, model.scale, groundNormal, model.lightSource, model.lightAmbientColor, model.lightDiffuseColor, model.palette, model.normalsPalette, model.shadowPalette); } diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index e479b68c58..ef2a2a8213 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -74,6 +74,10 @@ namespace OpenRA.Mods.Common.Traits [Desc("Boolean expression defining the condition under which this actor cannot be nudged by other actors.")] public readonly BooleanExpression ImmovableCondition = null; + [Desc("The distance from the edge of a cell over which the actor will adjust its tilt when moving between cells with different ramp types.", + "-1 means that the actor does not tilt on slopes.")] + public readonly WDist TerrainOrientationAdjustmentMargin = new WDist(-1); + IEnumerable IActorPreviewInitInfo.ActorPreviewInits(ActorInfo ai, ActorPreviewType type) { yield return new FacingInit(PreviewFacing); @@ -183,6 +187,7 @@ namespace OpenRA.Mods.Common.Traits } #endregion + WRot terrainRampOrientation = WRot.None; WAngle oldFacing; WRot orientation; WPos oldPos; @@ -211,7 +216,7 @@ namespace OpenRA.Mods.Common.Traits set => orientation = orientation.WithYaw(value); } - public WRot Orientation => orientation; + public WRot Orientation => orientation.Rotate(terrainRampOrientation); public WAngle TurnSpeed => Info.TurnSpeed; @@ -485,6 +490,9 @@ namespace OpenRA.Mods.Common.Traits CenterPosition = pos; self.World.UpdateMaps(self, this); + var map = self.World.Map; + SetTerrainRampOrientation(self, map.TerrainOrientation(map.CellContaining(pos))); + // The first time SetCenterPosition is called is in the constructor before creation, so we need a null check here as well if (notifyCenterPositionChanged == null) return; @@ -493,6 +501,12 @@ namespace OpenRA.Mods.Common.Traits n.CenterPositionChanged(self, fromCell.Layer, toCell.Layer); } + public void SetTerrainRampOrientation(Actor self, WRot orientation) + { + if (Info.TerrainOrientationAdjustmentMargin.Length >= 0) + terrainRampOrientation = orientation; + } + public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any) { return ToCell != location && fromCell == location diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index 9457958d39..9fc32c34fb 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -780,6 +780,7 @@ Locomotor: wheeled TurnSpeed: 20 Voice: Move + TerrainOrientationAdjustmentMargin: 256 Selectable: Bounds: 1206, 1448 Voiced: diff --git a/mods/ts/rules/gdi-vehicles.yaml b/mods/ts/rules/gdi-vehicles.yaml index 46f9230769..7d95c5feac 100644 --- a/mods/ts/rules/gdi-vehicles.yaml +++ b/mods/ts/rules/gdi-vehicles.yaml @@ -67,6 +67,7 @@ HVR: Mobile: Speed: 99 Locomotor: hover + TerrainOrientationAdjustmentMargin: -1 Selectable: Bounds: 1206, 1448, 0, -603 Health: @@ -123,6 +124,7 @@ SMECH: TurnSpeed: 20 Speed: 99 AlwaysTurnInPlace: true + TerrainOrientationAdjustmentMargin: -1 Health: HP: 17500 Armor: @@ -176,6 +178,7 @@ MMCH: TurnSpeed: 20 Speed: 56 AlwaysTurnInPlace: true + TerrainOrientationAdjustmentMargin: -1 Health: HP: 40000 Armor: @@ -341,6 +344,7 @@ JUGG: AlwaysTurnInPlace: true ImmovableCondition: !undeployed RequireForceMoveCondition: !undeployed + TerrainOrientationAdjustmentMargin: -1 RevealsShroud: RequiresCondition: !inside-tunnel Range: 9c0