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