diff --git a/MultiversalDiplomacy/Model/OrderParser.cs b/MultiversalDiplomacy/Model/OrderParser.cs index c9dd424..29461ec 100644 --- a/MultiversalDiplomacy/Model/OrderParser.cs +++ b/MultiversalDiplomacy/Model/OrderParser.cs @@ -266,15 +266,28 @@ public class OrderParser(World world) order = null; OrderParser re = new(world); - if (re.Hold.Match(command) is Match holdMatch && holdMatch.Success) { + if (re.Hold.Match(command) is Match holdMatch && holdMatch.Success) + { return TryParseHoldOrder(world, power, holdMatch, out order); - } else if (re.Move.Match(command) is Match moveMatch && moveMatch.Success) { + } + else if (re.Move.Match(command) is Match moveMatch && moveMatch.Success) + { return TryParseMoveOrder(world, power, moveMatch, out order); - } else if (re.SupportHold.Match(command) is Match sholdMatch && sholdMatch.Success) { + } + else if (re.SupportHold.Match(command) is Match sholdMatch && sholdMatch.Success) + { return TryParseSupportHoldOrder(world, power, sholdMatch, out order); - } else if (re.SupportMove.Match(command) is Match smoveMatch && smoveMatch.Success) { + } + else if (re.SupportMove.Match(command) is Match smoveMatch && smoveMatch.Success) + { return TryParseSupportMoveOrder(world, power, smoveMatch, out order); - } else { + } + else if (re.Convoy.Match(command) is Match convoyMatch && convoyMatch.Success) + { + return TryParseConvoyOrder(world, power, convoyMatch, out order); + } + else + { return false; } } @@ -495,4 +508,45 @@ public class OrderParser(World world) order = new SupportMoveOrder(power, subject, target, new(destTimeline, destTurn), destLocation); return true; } + + public static bool TryParseConvoyOrder( + World world, + string power, + Match match, + [NotNullWhen(true)] out Order? order) + { + order = null; + var convoy = ParseConvoy(match); + + if (!TryParseOrderSubject(world, convoy.timeline, convoy.turn, convoy.province, out Unit? subject)) { + return false; + } + + if (!TryParseOrderSubject( + world, convoy.targetTimeline, convoy.targetTurn, convoy.targetProvince, out Unit? target)) + { + return false; + } + + string destTimeline = convoy.destTimeline.Length > 0 + ? convoy.destTimeline + // If the destination is unspecified, use the target's + : target.Season.Timeline; + + int destTurn = convoy.destTurn.Length > 0 + ? int.Parse(convoy.destTurn) + // If the destination is unspecified, use the unit's + : target.Season.Turn; + + var destProvince = world.Map.Provinces.Single(province => province.Is(convoy.destProvince)); + + // Only armies can be convoyed, which means the destination location can only be land. + var landLocations = destProvince.Locations.Where(loc => loc.Type == LocationType.Land); + if (!landLocations.Any()) return false; // Can't convoy to water + string destLocationKey = landLocations.First().Key; + var destLocation = world.Map.GetLocation(destLocationKey); + + order = new ConvoyOrder(power, subject, target, new(destTimeline, destTurn), destLocation); + return true; + } } diff --git a/MultiversalDiplomacyTests/OrderParserTest.cs b/MultiversalDiplomacyTests/OrderParserTest.cs index 01cb3f3..5f62cd0 100644 --- a/MultiversalDiplomacyTests/OrderParserTest.cs +++ b/MultiversalDiplomacyTests/OrderParserTest.cs @@ -373,4 +373,17 @@ public class OrderParserTest Is.False, "Should not parse ambiguous coastal support"); } + + [Test] + public void DisambiguateConvoyDestination() + { + World world = World.WithStandardMap().AddUnits("France F MID", "France A Brest"); + + Assert.That( + OrderParser.TryParseOrder(world, "France", "MID C Brest - Spain", out Order? order), + "Failed to parse convoy order"); + Assert.That(order, Is.TypeOf(), "Unexpected order type"); + Location dest = ((ConvoyOrder)order!).Location; + Assert.That(dest.Name, Is.EqualTo("land"), "Unexpected destination location"); + } }