diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 12af338740..70d4d02774 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -213,6 +213,7 @@ namespace OpenRA.Traits bool AnyActorsAt(CPos a); bool AnyActorsAt(CPos a, SubCell sub, bool checkTransient = true); bool AnyActorsAt(CPos a, SubCell sub, Func withCondition); + IEnumerable AllActors(); void AddInfluence(Actor self, IOccupySpace ios); void RemoveInfluence(Actor self, IOccupySpace ios); int AddCellTrigger(CPos[] cells, Action onEntry, Action onExit); diff --git a/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs b/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs index 8b629d7c6c..31959e36d7 100644 --- a/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs +++ b/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; namespace OpenRA.Mods.Common.Pathfinder { @@ -74,17 +75,23 @@ namespace OpenRA.Mods.Common.Pathfinder /// This implementation is aware of movement costs over terrain given by /// . It is aware of changes to the costs in terrain and able to /// update the abstract graph when this occurs. It is able to search the abstract graph as if - /// had been specified. It is not aware of actors on the map. So blocking actors - /// will not be accounted for in the heuristic. + /// had been specified. If is given in the + /// constructor, the abstract graph will additionally account for a subset of immovable actors using the same rules + /// as . It will be aware of changes + /// to actors on the map and update the abstract graph when this occurs. Other types of blocking actors will not be + /// accounted for in the heuristic. /// /// If the obstacle on the map is from terrain (e.g. a cliff or lake) the heuristic will work well. If the - /// obstacle is from a blocking actor (trees, walls, buildings, units) the heuristic is unaware of these. Therefore - /// the same problem where the search goes in the wrong direction is possible, e.g. through a choke-point that has - /// been walled off. In this scenario the performance benefit will be lost, as the search will have to explore more - /// nodes until it can get around the obstacle. + /// obstacle is from the subset of immovable actors (e.g. trees, walls, buildings) and + /// was given, the heuristic will work well. If the obstacle is from other + /// actors (e.g. units) then the heuristic is unaware of these. Therefore the same problem where the search goes in + /// the wrong direction is possible, e.g. through a choke-point that has units blocking it. In this scenario the + /// performance benefit will be lost, as the search will have to explore more nodes until it can get around the + /// obstacle. /// /// In summary, the reduces the performance impact of path searches that - /// must go around terrain, but does not improve performance of searches that must go around actors. + /// must go around terrain, and some types of actor, but does not improve performance of searches that must go + /// around the remaining types of actor. /// public sealed class HierarchicalPathFinder { @@ -93,8 +100,10 @@ namespace OpenRA.Mods.Common.Pathfinder readonly World world; readonly Locomotor locomotor; + readonly IActorMap actorMap; readonly Func costEstimator; readonly HashSet dirtyGridIndexes = new HashSet(); + readonly HashSet cellsWithBlockingActor; Grid mapBounds; int gridXs; int gridYs; @@ -224,13 +233,34 @@ namespace OpenRA.Mods.Common.Pathfinder } } - public HierarchicalPathFinder(World world, Locomotor locomotor) + public HierarchicalPathFinder(World world, Locomotor locomotor, IActorMap actorMap, BlockedByActor check) { this.world = world; this.locomotor = locomotor; + this.actorMap = actorMap; if (locomotor.Info.TerrainSpeeds.Count == 0) return; + if (check == BlockedByActor.Immovable) + { + // When we account for immovable actors, it depends on the actors on the map. + // When this changes, we must update the cost table. + actorMap.CellUpdated += RequireBlockingRefreshInCell; + + // Determine immovable cells from actors already on the map. + cellsWithBlockingActor = actorMap.AllActors() + .Where(ActorIsBlocking) + .SelectMany(a => + a.OccupiesSpace.OccupiedCells() + .Select(oc => oc.Cell) + .Where(c => ActorCellIsBlocking(a, c))) + .ToHashSet(); + } + else if (check != BlockedByActor.None) + throw new System.ComponentModel.InvalidEnumArgumentException( + $"{nameof(HierarchicalPathFinder)} supports {nameof(BlockedByActor.None)} " + + $"and {nameof(BlockedByActor.Immovable)} only for {nameof(check)}"); + costEstimator = PathSearch.DefaultCostEstimator(locomotor); BuildGrids(); @@ -300,6 +330,12 @@ namespace OpenRA.Mods.Common.Pathfinder { var singleAbstractCellForLayer = new CPos?[customMovementLayers.Length]; var localCellToAbstractCell = new Dictionary(); + + // When accounting for immovable actors, use a custom cost so those cells become invalid paths. + var customCost = cellsWithBlockingActor == null + ? (Func)null + : c => cellsWithBlockingActor.Contains(c) ? PathGraph.PathCostForInvalidPath : 0; + for (byte gridLayer = 0; gridLayer < customMovementLayers.Length; gridLayer++) { if (gridLayer != 0 && @@ -314,7 +350,7 @@ namespace OpenRA.Mods.Common.Pathfinder for (var x = gridX; x < grid.BottomRight.X; x++) { var cell = new CPos(x, y, gridLayer); - if (locomotor.MovementCostForCell(cell) != PathGraph.MovementCostForUnreachableCell) + if (CellIsAccessible(cell)) accessibleCells.Add(cell); } } @@ -364,7 +400,7 @@ namespace OpenRA.Mods.Common.Pathfinder { var src = accessibleCells.First(); using (var search = GetLocalPathSearch( - null, new[] { src }, src, null, null, BlockedByActor.None, false, grid, 100)) + null, new[] { src }, src, customCost, null, BlockedByActor.None, false, grid, 100)) { var localCellsInRegion = search.ExpandAll(); var abstractCell = AbstractCellForLocalCells(localCellsInRegion, gridLayer); @@ -420,6 +456,24 @@ namespace OpenRA.Mods.Common.Pathfinder !customMovementLayers[gridLayer].EnabledForLocomotor(locomotor.Info))) continue; + void AddEdgesIfMovementAllowedBetweenCells(CPos cell, CPos candidateCell) + { + if (!MovementAllowedBetweenCells(cell, candidateCell)) + return; + + var gridInfo = gridInfos[GridIndex(cell)]; + var abstractCell = gridInfo.AbstractCellForLocalCell(cell); + if (abstractCell == null) + return; + + var gridInfoAdjacent = gridInfos[GridIndex(candidateCell)]; + var abstractCellAdjacent = gridInfoAdjacent.AbstractCellForLocalCell(candidateCell); + if (abstractCellAdjacent == null) + return; + + abstractEdges.Add((abstractCell.Value, abstractCellAdjacent.Value)); + } + // Searches along edges of all grids within a layer. // Checks for the local edge cell if we can traverse to any of the three adjacent cells in the next grid. // Builds connections in the abstract graph when any local cells have connections. @@ -432,28 +486,14 @@ namespace OpenRA.Mods.Common.Pathfinder for (var x = startX; x < startX + GridSize; x += xIncrement) { var cell = new CPos(x, y, gridLayer); - if (locomotor.MovementCostForCell(cell) == PathGraph.MovementCostForUnreachableCell) + if (!CellIsAccessible(cell)) continue; var adjacentCell = cell + adjacentVec; for (var i = -1; i <= 1; i++) { var candidateCell = adjacentCell + i * new CVec(adjacentVec.Y, adjacentVec.X); - if (locomotor.MovementCostToEnterCell(null, cell, candidateCell, BlockedByActor.None, null) != - PathGraph.MovementCostForUnreachableCell) - { - var gridInfo = gridInfos[GridIndex(cell)]; - var abstractCell = gridInfo.AbstractCellForLocalCell(cell); - if (abstractCell == null) - continue; - - var gridInfoAdjacent = gridInfos[GridIndex(candidateCell)]; - var abstractCellAdjacent = gridInfoAdjacent.AbstractCellForLocalCell(candidateCell); - if (abstractCellAdjacent == null) - continue; - - abstractEdges.Add((abstractCell.Value, abstractCellAdjacent.Value)); - } + AddEdgesIfMovementAllowedBetweenCells(cell, candidateCell); } } } @@ -479,7 +519,7 @@ namespace OpenRA.Mods.Common.Pathfinder for (var x = gridX; x < gridX + GridSize; x++) { var cell = new CPos(x, y, gridLayer); - if (locomotor.MovementCostForCell(cell) == PathGraph.MovementCostForUnreachableCell) + if (!CellIsAccessible(cell)) continue; CPos candidateCell; @@ -496,21 +536,7 @@ namespace OpenRA.Mods.Common.Pathfinder continue; } - if (locomotor.MovementCostToEnterCell(null, cell, candidateCell, BlockedByActor.None, null) == - PathGraph.MovementCostForUnreachableCell) - continue; - - var gridInfo = gridInfos[GridIndex(cell)]; - var abstractCell = gridInfo.AbstractCellForLocalCell(cell); - if (abstractCell == null) - continue; - - var gridInfoAdjacent = gridInfos[GridIndex(candidateCell)]; - var abstractCellAdjacent = gridInfoAdjacent.AbstractCellForLocalCell(candidateCell); - if (abstractCellAdjacent == null) - continue; - - abstractEdges.Add((abstractCell.Value, abstractCellAdjacent.Value)); + AddEdgesIfMovementAllowedBetweenCells(cell, candidateCell); } } } @@ -544,6 +570,98 @@ namespace OpenRA.Mods.Common.Pathfinder dirtyGridIndexes.Add(GridIndex(cell)); } + bool CellIsAccessible(CPos cell) + { + return locomotor.MovementCostForCell(cell) != PathGraph.MovementCostForUnreachableCell && + (cellsWithBlockingActor == null || !cellsWithBlockingActor.Contains(cell)); + } + + bool MovementAllowedBetweenCells(CPos accessibleSrcCell, CPos destCell) + { + return locomotor.MovementCostToEnterCell( + null, accessibleSrcCell, destCell, BlockedByActor.None, null) != PathGraph.MovementCostForUnreachableCell && + (cellsWithBlockingActor == null || !cellsWithBlockingActor.Contains(destCell)); + } + + /// + /// When actors change for a cell, marks the grid it belongs to as out of date. + /// + void RequireBlockingRefreshInCell(CPos cell) + { + var cellHasBlockingActor = false; + var actors = actorMap.GetActorsAt(cell); + foreach (var actor in actors) + { + if (ActorIsBlocking(actor) && ActorCellIsBlocking(actor, cell)) + { + cellHasBlockingActor = true; + break; + } + } + + if (cellHasBlockingActor) + { + if (cellsWithBlockingActor.Add(cell)) + dirtyGridIndexes.Add(GridIndex(cell)); + } + else + { + if (cellsWithBlockingActor.Remove(cell)) + dirtyGridIndexes.Add(GridIndex(cell)); + } + } + + /// + /// defines immovability based on the mobile trait. The blocking rules + /// in allow units to pass these + /// immovable actors if they are temporary blockers (e.g. gates) or crushable by the locomotor. Since our + /// abstract graph must work for any actor, we have to be conservative and can only consider a subset of the + /// immovable actors in the graph - ones we know cannot be passed by some actors due to these rules. + /// Both this and must be true for a cell to be blocked. + /// + /// This method is dependant on the logic in + /// and + /// . This method must be kept in sync with changes in the locomotor + /// rules. + /// + bool ActorIsBlocking(Actor actor) + { + var mobile = actor.OccupiesSpace as Mobile; + var isMovable = mobile != null && !mobile.IsTraitDisabled && !mobile.IsTraitPaused && !mobile.IsImmovable; + if (isMovable) + return false; + + var isTemporaryBlocker = world.RulesContainTemporaryBlocker && actor.TraitOrDefault() != null; + if (isTemporaryBlocker) + return false; + + var crushables = actor.TraitsImplementing(); + foreach (var crushable in crushables) + if (world.NoPlayersMask != crushable.CrushableBy(actor, locomotor.Info.Crushes)) + return false; + + return true; + } + + /// + /// The blocking rules additionally allow some cells to be considered passable even if the actor is blocking. + /// A cell is passable if the locomotor can share the cell and a subcell is available. It is also passable if + /// it is a transit only cell of a . We cannot consider these cells to be blocked. + /// Both this and must be true for a cell to be blocked. + /// + bool ActorCellIsBlocking(Actor actor, CPos cell) + { + var canShareCell = locomotor.Info.SharesCell && actorMap.HasFreeSubCell(cell); + if (canShareCell) + return false; + + var isTransitOnly = actor.OccupiesSpace is Building building && building.TransitOnlyCells().Contains(cell); + if (isTransitOnly) + return false; + + return true; + } + int GridIndex(CPos cellInGrid) { return @@ -741,7 +859,12 @@ namespace OpenRA.Mods.Common.Pathfinder /// /// Determines if a path exists between source and target. - /// Only terrain is taken into account, i.e. as if was given. + /// When was given, only terrain is taken into account, + /// i.e. as if was used when finding a path. + /// When was given, a subset of immovable actors are also taken into + /// account. If the method returns false, there is definitely no path. If it returns true there could be a + /// path, but it is possible that there is no path because of an immovable actor that does not belong to the + /// subset of actors that can be accounted for. So be careful. /// This would apply for any actor using the same as this . /// public bool PathExists(CPos source, CPos target) @@ -909,8 +1032,16 @@ namespace OpenRA.Mods.Common.Pathfinder // So we don't need to handle an abstract cell lookup failing, or the search failing to expand. // Cells added as initial starting points for the search are filtered out if they aren't reachable. // The search only explores accessible cells from then on. + // If the exceptions here do fire, they indicate a bug. The abstract graph is considering a cell to be + // unreachable, but the local pathfinder thinks it is reachable. We must fix the abstract graph to also + // consider the cell to be reachable. var gridInfo = gridInfos[GridIndex(cell)]; - var abstractCell = gridInfo.AbstractCellForLocalCell(cell).Value; + var maybeAbstractCell = gridInfo.AbstractCellForLocalCell(cell); + if (maybeAbstractCell == null) + throw new Exception( + "The abstract path should never be searched for an unreachable point. " + + "This is a bug. Failed lookup for an abstract cell."); + var abstractCell = maybeAbstractCell.Value; var info = graph[abstractCell]; // Expand the abstract search only if we have yet to get a route to the abstract cell. @@ -918,7 +1049,9 @@ namespace OpenRA.Mods.Common.Pathfinder { abstractSearch.TargetPredicate = c => c == abstractCell; if (!abstractSearch.ExpandToTarget()) - throw new Exception("The abstract path should never be searched for an unreachable point."); + throw new Exception( + "The abstract path should never be searched for an unreachable point. " + + "This is a bug. Failed to route to abstract cell."); info = graph[abstractCell]; } diff --git a/OpenRA.Mods.Common/Traits/World/ActorMap.cs b/OpenRA.Mods.Common/Traits/World/ActorMap.cs index be9d2088e7..e55bd3d432 100644 --- a/OpenRA.Mods.Common/Traits/World/ActorMap.cs +++ b/OpenRA.Mods.Common/Traits/World/ActorMap.cs @@ -403,6 +403,18 @@ namespace OpenRA.Mods.Common.Traits return AnyActorsAt(uv, layer, sub, withCondition); } + public IEnumerable AllActors() + { + foreach (var layer in influence) + { + if (layer == null) + continue; + foreach (var node in layer) + for (var i = node; i != null; i = i.Next) + yield return i.Actor; + } + } + public void AddInfluence(Actor self, IOccupySpace ios) { foreach (var c in ios.OccupiedCells()) diff --git a/OpenRA.Mods.Common/Traits/World/HierarchicalPathFinderOverlay.cs b/OpenRA.Mods.Common/Traits/World/HierarchicalPathFinderOverlay.cs index ded78433ba..3d16ffdb15 100644 --- a/OpenRA.Mods.Common/Traits/World/HierarchicalPathFinderOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/HierarchicalPathFinderOverlay.cs @@ -48,6 +48,11 @@ namespace OpenRA.Mods.Common.Traits /// public Locomotor Locomotor { get; set; } + /// + /// The blocking check selected in the UI which the overlay will display. + /// + public BlockedByActor Check { get; set; } = BlockedByActor.Immovable; + public HierarchicalPathFinderOverlay(HierarchicalPathFinderOverlayInfo info) { this.info = info; @@ -88,7 +93,7 @@ namespace OpenRA.Mods.Common.Traits : new[] { Locomotor }; foreach (var locomotor in locomotors) { - var (abstractGraph, abstractDomains) = pathFinder.GetOverlayDataForLocomotor(locomotor); + var (abstractGraph, abstractDomains) = pathFinder.GetOverlayDataForLocomotor(locomotor, Check); // Locomotor doesn't allow movement, nothing to display. if (abstractGraph == null || abstractDomains == null) diff --git a/OpenRA.Mods.Common/Traits/World/Locomotor.cs b/OpenRA.Mods.Common/Traits/World/Locomotor.cs index 59f8c866a7..f53ec7fde6 100644 --- a/OpenRA.Mods.Common/Traits/World/Locomotor.cs +++ b/OpenRA.Mods.Common/Traits/World/Locomotor.cs @@ -311,6 +311,9 @@ namespace OpenRA.Mods.Common.Traits return world.ActorMap.FreeSubCell(cell, preferredSubCell); } + /// This logic is replicated in and + /// . If this method is updated please update those as + /// well. bool IsBlockedBy(Actor actor, Actor otherActor, Actor ignoreActor, CPos cell, BlockedByActor check, CellFlag cellFlag) { if (otherActor == ignoreActor) @@ -450,6 +453,9 @@ namespace OpenRA.Mods.Common.Traits } } + /// This logic is replicated in and + /// . If this method is updated please update those as + /// well. void UpdateCellBlocking(CPos cell) { using (new PerfSample("locomotor_cache")) diff --git a/OpenRA.Mods.Common/Traits/World/PathFinder.cs b/OpenRA.Mods.Common/Traits/World/PathFinder.cs index d3e18ba674..d902fadb4f 100644 --- a/OpenRA.Mods.Common/Traits/World/PathFinder.cs +++ b/OpenRA.Mods.Common/Traits/World/PathFinder.cs @@ -19,8 +19,8 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [TraitLocation(SystemActors.World)] - [Desc("Calculates routes for mobile units with locomotors based on the A* search algorithm.", " Attach this to the world actor.")] - public class PathFinderInfo : TraitInfo, Requires + [Desc("Calculates routes for mobile actors with locomotors based on the A* search algorithm.", " Attach this to the world actor.")] + public class PathFinderInfo : TraitInfo, Requires, Requires { public override object Create(ActorInitializer init) { @@ -40,7 +40,8 @@ namespace OpenRA.Mods.Common.Traits readonly World world; PathFinderOverlay pathFinderOverlay; - Dictionary hierarchicalPathFindersByLocomotor; + Dictionary hierarchicalPathFindersBlockedByNoneByLocomotor; + Dictionary hierarchicalPathFindersBlockedByImmovableByLocomotor; public PathFinder(Actor self) { @@ -49,9 +50,10 @@ namespace OpenRA.Mods.Common.Traits public ( IReadOnlyDictionary> AbstractGraph, - IReadOnlyDictionary AbstractDomains) GetOverlayDataForLocomotor(Locomotor locomotor) + IReadOnlyDictionary AbstractDomains) GetOverlayDataForLocomotor( + Locomotor locomotor, BlockedByActor check) { - return hierarchicalPathFindersByLocomotor[locomotor].GetOverlayData(); + return GetHierarchicalPathFinder(locomotor, check, null).GetOverlayData(); } public void WorldLoaded(World w, WorldRenderer wr) @@ -59,9 +61,13 @@ namespace OpenRA.Mods.Common.Traits pathFinderOverlay = world.WorldActor.TraitOrDefault(); // Requires ensures all Locomotors have been initialized. - hierarchicalPathFindersByLocomotor = w.WorldActor.TraitsImplementing().ToDictionary( + var locomotors = w.WorldActor.TraitsImplementing().ToList(); + hierarchicalPathFindersBlockedByNoneByLocomotor = locomotors.ToDictionary( locomotor => locomotor, - locomotor => new HierarchicalPathFinder(world, locomotor)); + locomotor => new HierarchicalPathFinder(world, locomotor, w.ActorMap, BlockedByActor.None)); + hierarchicalPathFindersBlockedByImmovableByLocomotor = locomotors.ToDictionary( + locomotor => locomotor, + locomotor => new HierarchicalPathFinder(world, locomotor, w.ActorMap, BlockedByActor.Immovable)); } /// @@ -110,15 +116,25 @@ namespace OpenRA.Mods.Common.Traits } // Use a hierarchical path search, which performs a guided bidirectional search. - return hierarchicalPathFindersByLocomotor[locomotor].FindPath( + return GetHierarchicalPathFinder(locomotor, check, ignoreActor).FindPath( self, source, target, check, DefaultHeuristicWeightPercentage, customCost, ignoreActor, laneBias, pathFinderOverlay); } // Use a hierarchical path search, which performs a guided unidirectional search. - return hierarchicalPathFindersByLocomotor[locomotor].FindPath( + return GetHierarchicalPathFinder(locomotor, check, ignoreActor).FindPath( self, sourcesList, target, check, DefaultHeuristicWeightPercentage, customCost, ignoreActor, laneBias, pathFinderOverlay); } + HierarchicalPathFinder GetHierarchicalPathFinder(Locomotor locomotor, BlockedByActor check, Actor ignoreActor) + { + // If there is an actor to ignore, we cannot use an HPF that accounts for any blocking actors. + // One of the blocking actors might be the one we need to ignore! + var hpfs = check == BlockedByActor.None || ignoreActor != null + ? hierarchicalPathFindersBlockedByNoneByLocomotor + : hierarchicalPathFindersBlockedByImmovableByLocomotor; + return hpfs[locomotor]; + } + /// /// Calculates a path for the actor from multiple possible sources, whilst searching for an acceptable target. /// Returned path is *reversed* and given target to source. @@ -149,7 +165,7 @@ namespace OpenRA.Mods.Common.Traits /// public bool PathExistsForLocomotor(Locomotor locomotor, CPos source, CPos target) { - return hierarchicalPathFindersByLocomotor[locomotor].PathExists(source, target); + return hierarchicalPathFindersBlockedByNoneByLocomotor[locomotor].PathExists(source, target); } static Locomotor GetActorLocomotor(Actor self) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/HierarchicalPathFinderOverlayLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/HierarchicalPathFinderOverlayLogic.cs index a022b2ee31..30f0c6bbfe 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/HierarchicalPathFinderOverlayLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/HierarchicalPathFinderOverlayLogic.cs @@ -42,6 +42,23 @@ namespace OpenRA.Mods.Common.Widgets.Logic locomotorSelector.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", locomotors.Length * 30, locomotors, setupItem); }; + + var checks = new[] { BlockedByActor.None, BlockedByActor.Immovable }; + var checkSelector = widget.Get("HPF_OVERLAY_CHECK"); + checkSelector.OnMouseDown = _ => + { + Func setupItem = (option, template) => + { + var item = ScrollItemWidget.Setup( + template, + () => hpfOverlay.Check == option, + () => hpfOverlay.Check = option); + item.Get("LABEL").GetText = () => option.ToString(); + return item; + }; + + checkSelector.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", checks.Length * 30, checks, setupItem); + }; } } } diff --git a/mods/cnc/chrome/ingame.yaml b/mods/cnc/chrome/ingame.yaml index 95e920a893..88244efa31 100644 --- a/mods/cnc/chrome/ingame.yaml +++ b/mods/cnc/chrome/ingame.yaml @@ -1864,14 +1864,21 @@ Container@PLAYER_WIDGETS: Logic: HierarchicalPathFinderOverlayLogic X: WINDOW_RIGHT - WIDTH - 240 Y: 40 - Width: 150 - Height: 25 + Width: 175 + Height: 60 Children: DropDownButton@HPF_OVERLAY_LOCOMOTOR: + Y: PARENT_TOP Width: PARENT_RIGHT - Height: PARENT_BOTTOM + Height: 25 Text: Select Locomotor Font: Regular + DropDownButton@HPF_OVERLAY_CHECK: + Y: PARENT_TOP + 35 + Width: PARENT_RIGHT + Height: 25 + Text: Select BlockedByActor + Font: Regular Background@FMVPLAYER: Width: WINDOW_RIGHT diff --git a/mods/d2k/chrome/ingame-player.yaml b/mods/d2k/chrome/ingame-player.yaml index c6532e20f8..108dcbfcf7 100644 --- a/mods/d2k/chrome/ingame-player.yaml +++ b/mods/d2k/chrome/ingame-player.yaml @@ -645,11 +645,18 @@ Container@PLAYER_WIDGETS: Logic: HierarchicalPathFinderOverlayLogic X: WINDOW_RIGHT - WIDTH - 231 Y: 40 - Width: 150 - Height: 25 + Width: 175 + Height: 60 Children: DropDownButton@HPF_OVERLAY_LOCOMOTOR: + Y: PARENT_TOP Width: PARENT_RIGHT - Height: PARENT_BOTTOM + Height: 25 Text: Select Locomotor Font: Regular + DropDownButton@HPF_OVERLAY_CHECK: + Y: PARENT_TOP + 35 + Width: PARENT_RIGHT + Height: 25 + Text: Select BlockedByActor + Font: Regular diff --git a/mods/ra/chrome/ingame-player.yaml b/mods/ra/chrome/ingame-player.yaml index 0894249fa9..d60270d5c7 100644 --- a/mods/ra/chrome/ingame-player.yaml +++ b/mods/ra/chrome/ingame-player.yaml @@ -653,12 +653,19 @@ Container@PLAYER_WIDGETS: Logic: HierarchicalPathFinderOverlayLogic X: WINDOW_RIGHT - WIDTH - 260 Y: 40 - Width: 150 - Height: 25 + Width: 175 + Height: 60 Children: DropDownButton@HPF_OVERLAY_LOCOMOTOR: + Y: PARENT_TOP Width: PARENT_RIGHT - Height: PARENT_BOTTOM + Height: 25 Text: Select Locomotor Font: Regular + DropDownButton@HPF_OVERLAY_CHECK: + Y: PARENT_TOP + 35 + Width: PARENT_RIGHT + Height: 25 + Text: Select BlockedByActor + Font: Regular diff --git a/mods/ts/chrome/ingame-player.yaml b/mods/ts/chrome/ingame-player.yaml index 202048864c..5ba58b111f 100644 --- a/mods/ts/chrome/ingame-player.yaml +++ b/mods/ts/chrome/ingame-player.yaml @@ -623,11 +623,18 @@ Container@PLAYER_WIDGETS: Logic: HierarchicalPathFinderOverlayLogic X: WINDOW_RIGHT - WIDTH - 245 Y: 40 - Width: 150 - Height: 25 + Width: 175 + Height: 60 Children: DropDownButton@HPF_OVERLAY_LOCOMOTOR: + Y: PARENT_TOP Width: PARENT_RIGHT - Height: PARENT_BOTTOM + Height: 25 Text: Select Locomotor Font: Regular + DropDownButton@HPF_OVERLAY_CHECK: + Y: PARENT_TOP + 35 + Width: PARENT_RIGHT + Height: 25 + Text: Select BlockedByActor + Font: Regular