In a multiplayer game with bots running, only the host client runs bot logic. This logic must not alter the world state, or the host will desync from the other clients which do not run the bot logic.
The bot logic can interact with the pathfinder. This can eventually flow into HierarchicalPathFinder.BuildGrid which calls AbstractCellForLocalCells to determine the abstract cells within the grid.
This method chooses an abstract cell closest to the middle, but there can be multiple cells tied for this. The input comes from a local path search, whose ordering depends on current cell costs.
Previously, this ordering could would influence which of the equally close cells we would choose. In this case we'd choose the first in the ordering.
On non-host clients which don't run the bot logic, they may not build the grid until a later tick. If the cell costs have changed the the local path search would still explore the same set of cells, but the ordering may be different.
With the previous behaviour, they could choose a different abstract cell from the equally close cells. This results in a diverged state where their abstract graph differs subtly from the host abstract graph.
Eventually, a unit will path search in a manner where this state divergence causes it to take a different path, which will be detected as a desync.
To fix the issue, we ensure we pick the same abstract cell no matter the ordering of the list, by inserting an arbitrary tiebreaker rule.
This means the differing ordering of the explored cells from the local path search is still possible, but the ordering no longer influences the abstract cell we choose as a result.
This keeps the state of the abstract graph in sync across the clients, and thus prevents units from making different pathfinding decisions and desyncing.
Renames the "Experimental" map generator to "Classic" in internal
code, configuration, and user-facing text.
Additionally, replace the user-facing names for the Experimental and
D2K map generators (as seen in the map editor tools dropdown) with
"Map Generator".
The map generators previously placed spawns in an order that was
somewhat arbitrary and unintuitive to users.
This change provides and makes use of a Terraformer utility to reorder
spawns, based on their location, so that they typically make more
sense to users selecting spawns from a lobby.
Added to ExperimentalMapGenerator (RA, CnC), and D2kMapGenerator.
Note that as this changes the output of the map generator for a given
selection of settings. (Replays using generated maps from older
versions will lose compatibility and no longer load.)
Fix erroneous minimap selection cursor when not in classic mode
Fix selection cursor not showing up with units selected when not in classic mouse style
Uses left-click for targeted orders (attack move, guard, selecting movement types from the command bar, etc.) and right-click for contextual ones. Works like basically all non-C&C games
Removed GameSettings.UseClassicMouseStyle, as it is replaced by MouseControlStyle
Changed Minelayer and Chronotank teleport to UnitOrderGenerator
Fixes bug with Modern controls issuing a move order cancelling a mine field order
Better organizes all unit orders into a single order generator base class, rather than having their logic spread across multiple base classes, which required some hacks to get the control logic to match
Fixes regression from 649e7e8c28. When the Templates was changed from a Dictionary to a FrozenDictionary, this caused the items to be reordered. Some code was relying on the previous behaviour where the Dictionary ordering would happen to remain the same as the YAML input.
Fix this by creating a TemplatesInDefinitionOrder which can be used to iterate in the same ordering as given in YAML. Code that depends on the ordering can use this, and other code that does lookups can use the FrozenDictionary.
Some common feedback about preview generated maps is that they are too
chokey. This change aims to add a new terrain type called "Plots" that
retains the variety of terrain features present in the Gardens
terrain, but opens up the play area a bit more. In essence, it strikes
a balance between the densely packed Gardens terrain type and the
loosely packed Plains and Parks terrain types.
This new terrain type notably uses a smaller noise feature size to
provide smaller but more regular map structure, and expands the
pathway cutouts and contour spacings for forests and cliffs.
The new Plots terrain type is made available for both RA and CNC and
becomes the new default.
This is an additive configuration-only change. Replay and multiplayer
map generation retains forward/backward compatibility.
Both GetPosition and LandPosition were trying to solve the problem
of dynamic altitude offsets for carryalls with different-sized cargo.
However, because they both apply the offsets, when the two are used
together the two sets of offsets cancel to 0, producing incorrect behaviour.
GetPosition is removed in favor of using CenterPosition with the dynamic
LandAltitude. Fly.Tick and Carryall ignore the z coordinate so their
behaviour is unchanged. Land and Aircraft.AtLandAltitude now return
the correct result.
As the InitDict is lazy, this removes the possibility for a mutable collection to be used but the values not to be consumed until later, which would be confusing. As all existing callers actually create a throw-away dictionary from MiniYaml, this is an easy switch.
The map generator is already performant, but we can remove a few rough edges from infrastructure it interacts with.
- Add TypeDictionary constructor to clone from an existing copy. The map editor infrastructure relies heavily on cloning ActorReferences and the underlying dictionaries. A dedicated clone method avoids reflection overhead for CreateTypeContainer on a fresh instance.
- Use ThrowIndexOutOfRangeException helper. This allows the Index method to be inlined.
- Reuse lists in FloodFill. This avoids allocating and resizing new lists on each loop when we can reuse the existing capacity.
- Manage CellEntryChanged at the layer rather than individual previews. We only need to attach/detach from the delegate once at the layer, rather than many times as previews are added and removed.