diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index e8d165d4c7..07c8901d4d 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -794,6 +794,8 @@
+
+
diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs
index 4f2393b6e4..e139614632 100644
--- a/OpenRA.Mods.Common/Traits/Mobile.cs
+++ b/OpenRA.Mods.Common/Traits/Mobile.cs
@@ -38,6 +38,11 @@ namespace OpenRA.Mods.Common.Traits
}
}
+ public static class CustomMovementLayerType
+ {
+ public const byte Tunnel = 1;
+ }
+
[Desc("Unit is able to move.")]
public class MobileInfo : ConditionalTraitInfo, IMoveInfo, IPositionableInfo, IOccupySpaceInfo, IFacingInfo,
UsesInit, UsesInit, UsesInit
@@ -73,6 +78,10 @@ namespace OpenRA.Mods.Common.Traits
[VoiceReference] public readonly string Voice = "Action";
+ [GrantedConditionReference]
+ [Desc("The condition to grant to self while inside a tunnel.")]
+ public readonly string TunnelCondition = null;
+
public override object Create(ActorInitializer init) { return new Mobile(init, this); }
static object LoadSpeeds(MiniYaml y)
@@ -319,8 +328,8 @@ namespace OpenRA.Mods.Common.Traits
bool IOccupySpaceInfo.SharesCell { get { return SharesCell; } }
}
- public class Mobile : ConditionalTrait, IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove, IFacing, ISync,
- IDeathActorInitModifier, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyBlockingMove, IActorPreviewInitModifier
+ public class Mobile : ConditionalTrait, INotifyCreated, IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove,
+ IFacing, ISync, IDeathActorInitModifier, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyBlockingMove, IActorPreviewInitModifier
{
const int AverageTicksBeforePathing = 5;
const int SpreadTicksBeforePathing = 5;
@@ -334,6 +343,8 @@ namespace OpenRA.Mods.Common.Traits
int facing;
CPos fromCell, toCell;
public SubCell FromSubCell, ToSubCell;
+ int tunnelToken = ConditionManager.InvalidConditionToken;
+ ConditionManager conditionManager;
[Sync] public int Facing
{
@@ -361,6 +372,12 @@ namespace OpenRA.Mods.Common.Traits
FromSubCell = fromSub;
ToSubCell = toSub;
AddInfluence();
+
+ if (toCell.Layer == CustomMovementLayerType.Tunnel && conditionManager != null &&
+ !string.IsNullOrEmpty(Info.TunnelCondition) && tunnelToken == ConditionManager.InvalidConditionToken)
+ tunnelToken = conditionManager.GrantCondition(self, Info.TunnelCondition);
+ else if (toCell.Layer != CustomMovementLayerType.Tunnel && tunnelToken != ConditionManager.InvalidConditionToken)
+ tunnelToken = conditionManager.RevokeCondition(self, tunnelToken);
}
public Mobile(ActorInitializer init, MobileInfo info)
@@ -388,6 +405,11 @@ namespace OpenRA.Mods.Common.Traits
SetVisualPosition(self, init.Get());
}
+ void INotifyCreated.Created(Actor self)
+ {
+ conditionManager = self.TraitOrDefault();
+ }
+
// Returns a valid sub-cell
public SubCell GetValidSubCell(SubCell preferred = SubCell.Any)
{
diff --git a/OpenRA.Mods.Common/Traits/World/CliffBackImpassabilityLayer.cs b/OpenRA.Mods.Common/Traits/World/CliffBackImpassabilityLayer.cs
index d7ac69f046..88efcfcf85 100644
--- a/OpenRA.Mods.Common/Traits/World/CliffBackImpassabilityLayer.cs
+++ b/OpenRA.Mods.Common/Traits/World/CliffBackImpassabilityLayer.cs
@@ -9,6 +9,7 @@
*/
#endregion
+using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Traits;
@@ -36,8 +37,17 @@ namespace OpenRA.Mods.Common.Traits
public void WorldLoaded(World w, WorldRenderer wr)
{
var tileType = w.Map.Rules.TileSet.GetTerrainIndex(info.TerrainType);
+
+ // Units are allowed behind cliffs *only* if they are part of a tunnel portal
+ var tunnelPortals = w.WorldActor.Info.TraitInfos()
+ .SelectMany(mti => mti.PortalCells())
+ .ToHashSet();
+
foreach (var uv in w.Map.AllCells.MapCoords)
{
+ if (tunnelPortals.Contains(uv.ToCPos(w.Map)))
+ continue;
+
// All the map cells that visually overlap the current cell
var testCells = w.Map.ProjectedCellsCovering(uv)
.SelectMany(puv => w.Map.Unproject(puv));
diff --git a/OpenRA.Mods.Common/Traits/World/TerrainTunnel.cs b/OpenRA.Mods.Common/Traits/World/TerrainTunnel.cs
new file mode 100644
index 0000000000..14f7d14b58
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/World/TerrainTunnel.cs
@@ -0,0 +1,62 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2017 The OpenRA Developers (see AUTHORS)
+ * This file is part of OpenRA, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+#endregion
+
+using System.Collections.Generic;
+using System.Linq;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ public class TerrainTunnelInfo : TraitInfo, Requires, ILobbyCustomRulesIgnore
+ {
+ [FieldLoader.Require]
+ [Desc("Location of the tunnel")]
+ public readonly CPos Location = CPos.Zero;
+
+ [FieldLoader.Require]
+ [Desc("Height of the tunnel floor in map height steps.")]
+ public readonly byte Height = 0;
+
+ [FieldLoader.Require]
+ [Desc("Size of the tunnel footprint")]
+ public readonly CVec Dimensions = CVec.Zero;
+
+ [FieldLoader.Require]
+ [Desc("Tunnel footprint.", "_ is passable, x is blocked, and o are tunnel portals.")]
+ public readonly string Footprint = string.Empty;
+
+ [FieldLoader.Require]
+ [Desc("Terrain type of the tunnel floor.")]
+ public readonly string TerrainType = null;
+
+ public IEnumerable TunnelCells()
+ {
+ return CellsMatching('_').Concat(CellsMatching('o'));
+ }
+
+ public IEnumerable PortalCells()
+ {
+ return CellsMatching('o');
+ }
+
+ IEnumerable CellsMatching(char c)
+ {
+ var index = 0;
+ var footprint = Footprint.Where(x => !char.IsWhiteSpace(x)).ToArray();
+ for (var y = 0; y < Dimensions.Y; y++)
+ for (var x = 0; x < Dimensions.X; x++)
+ if (footprint[index++] == c)
+ yield return Location + new CVec(x, y);
+ }
+ }
+
+ public class TerrainTunnel { }
+}
diff --git a/OpenRA.Mods.Common/Traits/World/TerrainTunnelLayer.cs b/OpenRA.Mods.Common/Traits/World/TerrainTunnelLayer.cs
new file mode 100644
index 0000000000..6355c4b60c
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/World/TerrainTunnelLayer.cs
@@ -0,0 +1,94 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2017 The OpenRA Developers (see AUTHORS)
+ * This file is part of OpenRA, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+#endregion
+
+using System.Collections.Generic;
+using OpenRA.Graphics;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ public class TerrainTunnelLayerInfo : ITraitInfo, Requires, ILobbyCustomRulesIgnore
+ {
+ [Desc("Terrain type used by cells outside any tunnel footprint.")]
+ public readonly string ImpassableTerrainType = "Impassable";
+
+ public object Create(ActorInitializer init) { return new TerrainTunnelLayer(init.Self, this); }
+ }
+
+ public class TerrainTunnelLayer : ICustomMovementLayer, IWorldLoaded
+ {
+ readonly Map map;
+ readonly CellLayer cellCenters;
+ readonly CellLayer terrainIndices;
+ readonly HashSet portals = new HashSet();
+ bool enabled;
+
+ public TerrainTunnelLayer(Actor self, TerrainTunnelLayerInfo info)
+ {
+ map = self.World.Map;
+ cellCenters = new CellLayer(map);
+ terrainIndices = new CellLayer(map);
+ terrainIndices.Clear(map.Rules.TileSet.GetTerrainIndex(info.ImpassableTerrainType));
+ }
+
+ public void WorldLoaded(World world, WorldRenderer wr)
+ {
+ var domainIndex = world.WorldActor.Trait();
+ foreach (var tti in world.WorldActor.Info.TraitInfos())
+ {
+ enabled = true;
+
+ var terrain = map.Rules.TileSet.GetTerrainIndex(tti.TerrainType);
+ foreach (var c in tti.TunnelCells())
+ {
+ var uv = c.ToMPos(map);
+ terrainIndices[uv] = terrain;
+
+ var pos = map.CenterOfCell(c);
+ cellCenters[uv] = pos - new WVec(0, 0, pos.Z - 512 * tti.Height);
+ }
+
+ var portal = tti.PortalCells();
+ domainIndex.AddFixedConnection(portal);
+ foreach (var c in portal)
+ {
+ // Need to explicitly set both default and tunnel layers, otherwise the .Contains check will fail
+ portals.Add(new CPos(c.X, c.Y, 0));
+ portals.Add(new CPos(c.X, c.Y, CustomMovementLayerType.Tunnel));
+ }
+ }
+ }
+
+ bool ICustomMovementLayer.EnabledForActor(ActorInfo a, MobileInfo mi) { return enabled; }
+ byte ICustomMovementLayer.Index { get { return CustomMovementLayerType.Tunnel; } }
+ bool ICustomMovementLayer.InteractsWithDefaultLayer { get { return false; } }
+
+ WPos ICustomMovementLayer.CenterOfCell(CPos cell)
+ {
+ return cellCenters[cell];
+ }
+
+ int ICustomMovementLayer.EntryMovementCost(ActorInfo a, MobileInfo mi, CPos cell)
+ {
+ return portals.Contains(cell) ? 0 : int.MaxValue;
+ }
+
+ int ICustomMovementLayer.ExitMovementCost(ActorInfo a, MobileInfo mi, CPos cell)
+ {
+ return portals.Contains(cell) ? 0 : int.MaxValue;
+ }
+
+ byte ICustomMovementLayer.GetTerrainIndex(CPos cell)
+ {
+ return terrainIndices[cell];
+ }
+ }
+}
diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml
index 19235db5f9..de69cb9412 100644
--- a/mods/ts/rules/defaults.yaml
+++ b/mods/ts/rules/defaults.yaml
@@ -306,6 +306,7 @@
Voiced:
VoiceSet: Infantry
Targetable:
+ RequiresCondition: !inside-tunnel
TargetTypes: Ground, Infantry
QuantizeFacingsFromSequence:
Sequence: stand
@@ -479,6 +480,7 @@
Voiced:
VoiceSet: Vehicle
Targetable:
+ RequiresCondition: !inside-tunnel
TargetTypes: Ground, Vehicle, Repair
Repairable:
RepairBuildings: gadept
@@ -521,6 +523,7 @@
RequiresCondition: criticalspeed
Modifier: 60
Carryable:
+ RequiresCondition: !inside-tunnel
RevealOnFire:
^Tank:
diff --git a/mods/ts/rules/gdi-vehicles.yaml b/mods/ts/rules/gdi-vehicles.yaml
index fac276e08c..7f18ad89b1 100644
--- a/mods/ts/rules/gdi-vehicles.yaml
+++ b/mods/ts/rules/gdi-vehicles.yaml
@@ -41,6 +41,7 @@ APC:
Sequence: water
RequiresCondition: inwater
LeavesTrails:
+ RequiresCondition: !inside-tunnel
Image: wake
Palette: effect
TerrainTypes: Water
@@ -92,6 +93,7 @@ HVR:
WithVoxelTurret:
Hovers:
LeavesTrails:
+ RequiresCondition: !inside-tunnel
Image: wake
Palette: effect
TerrainTypes: Water
diff --git a/mods/ts/sequences/misc.yaml b/mods/ts/sequences/misc.yaml
index 5bd7905ab1..12c20edd12 100644
--- a/mods/ts/sequences/misc.yaml
+++ b/mods/ts/sequences/misc.yaml
@@ -616,7 +616,9 @@ tracks16:
Offset: 0, -13, 42
ZRamp: 1
UseTilesetExtension: true
- ShadowStart: 1
+ # Disabled to avoid glitches until shadow rendering is fixed
+ # Appears to be unused in the original game too
+ # ShadowStart: 1
tuntop01:
Inherits: ^tuntop