Eliminate sources of Unit reference equality
This commit is contained in:
parent
9a64609605
commit
eaafdeb5a9
|
@ -7,7 +7,7 @@ namespace MultiversalDiplomacy.Adjudicate.Decision;
|
||||||
|
|
||||||
public class MovementDecisions
|
public class MovementDecisions
|
||||||
{
|
{
|
||||||
public Dictionary<Unit, IsDislodged> IsDislodged { get; }
|
public Dictionary<string, IsDislodged> IsDislodged { get; }
|
||||||
public Dictionary<MoveOrder, HasPath> HasPath { get; }
|
public Dictionary<MoveOrder, HasPath> HasPath { get; }
|
||||||
public Dictionary<SupportOrder, GivesSupport> GivesSupport { get; }
|
public Dictionary<SupportOrder, GivesSupport> GivesSupport { get; }
|
||||||
public Dictionary<(string, string), HoldStrength> HoldStrength { get; }
|
public Dictionary<(string, string), HoldStrength> HoldStrength { get; }
|
||||||
|
@ -113,7 +113,7 @@ public class MovementDecisions
|
||||||
&& SplitKey(other.Location).province == world.Map.GetLocation(me.Unit).Province.Name;
|
&& SplitKey(other.Location).province == world.Map.GetLocation(me.Unit).Province.Name;
|
||||||
|
|
||||||
bool IsSupportFor(SupportMoveOrder me, MoveOrder move)
|
bool IsSupportFor(SupportMoveOrder me, MoveOrder move)
|
||||||
=> me.Target == move.Unit
|
=> me.Target.Key == move.Unit.Key
|
||||||
&& me.Season == move.Season
|
&& me.Season == move.Season
|
||||||
&& me.Location.Key == move.Location;
|
&& me.Location.Key == move.Location;
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ public class MovementDecisions
|
||||||
.OfType<MoveOrder>()
|
.OfType<MoveOrder>()
|
||||||
.Where(other => IsIncoming(order, other))
|
.Where(other => IsIncoming(order, other))
|
||||||
.ToList();
|
.ToList();
|
||||||
IsDislodged[order.Unit] = new(order, incoming);
|
IsDislodged[order.Unit.Key] = new(order, incoming);
|
||||||
|
|
||||||
if (order is MoveOrder move)
|
if (order is MoveOrder move)
|
||||||
{
|
{
|
||||||
|
|
|
@ -163,7 +163,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
|
|
||||||
// Support-hold orders are invalid if the unit supports itself.
|
// Support-hold orders are invalid if the unit supports itself.
|
||||||
AdjudicatorHelpers.InvalidateIfNotMatching(
|
AdjudicatorHelpers.InvalidateIfNotMatching(
|
||||||
order => order.Unit != order.Target,
|
order => order.Unit.Key != order.Target.Key,
|
||||||
ValidationReason.NoSelfSupport,
|
ValidationReason.NoSelfSupport,
|
||||||
ref supportHoldOrders,
|
ref supportHoldOrders,
|
||||||
ref validationResults);
|
ref validationResults);
|
||||||
|
@ -241,13 +241,13 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
// were not addressed by 4.D.1-2 and will be handled according to 4.D.3, i.e. replaced with
|
// were not addressed by 4.D.1-2 and will be handled according to 4.D.3, i.e. replaced with
|
||||||
// hold orders. Note that this happens last, after all other invalidations have been
|
// hold orders. Note that this happens last, after all other invalidations have been
|
||||||
// applied in order to comply with what 4.D.3 specifies about illegal orders.
|
// applied in order to comply with what 4.D.3 specifies about illegal orders.
|
||||||
List<Unit> duplicateOrderedUnits = unitOrders
|
List<string> duplicateOrderedUnits = unitOrders
|
||||||
.GroupBy(o => o.Unit)
|
.GroupBy(o => o.Unit.Key)
|
||||||
.Where(orderGroup => orderGroup.Count() > 1)
|
.Where(orderGroup => orderGroup.Count() > 1)
|
||||||
.Select(orderGroup => orderGroup.Key)
|
.Select(orderGroup => orderGroup.Key)
|
||||||
.ToList();
|
.ToList();
|
||||||
List<UnitOrder> duplicateOrders = unitOrders
|
List<UnitOrder> duplicateOrders = unitOrders
|
||||||
.Where(o => duplicateOrderedUnits.Contains(o.Unit))
|
.Where(o => duplicateOrderedUnits.Contains(o.Unit.Key))
|
||||||
.ToList();
|
.ToList();
|
||||||
List<UnitOrder> validOrders = unitOrders.Except(duplicateOrders).ToList();
|
List<UnitOrder> validOrders = unitOrders.Except(duplicateOrders).ToList();
|
||||||
validationResults = validationResults
|
validationResults = validationResults
|
||||||
|
@ -259,9 +259,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
List<Unit> allOrderableUnits = world.Units
|
List<Unit> allOrderableUnits = world.Units
|
||||||
.Where(unit => !world.Timelines.GetFutures(unit.Season).Any())
|
.Where(unit => !world.Timelines.GetFutures(unit.Season).Any())
|
||||||
.ToList();
|
.ToList();
|
||||||
HashSet<Unit> orderedUnits = validOrders.Select(order => order.Unit).ToHashSet();
|
HashSet<string> orderedUnits = validOrders.Select(order => order.Unit.Key).ToHashSet();
|
||||||
List<Unit> unorderedUnits = allOrderableUnits
|
List<Unit> unorderedUnits = allOrderableUnits
|
||||||
.Where(unit => !orderedUnits.Contains(unit))
|
.Where(unit => !orderedUnits.Contains(unit.Key))
|
||||||
.ToList();
|
.ToList();
|
||||||
List<HoldOrder> implicitHolds = unorderedUnits
|
List<HoldOrder> implicitHolds = unorderedUnits
|
||||||
.Select(unit => new HoldOrder(unit.Power, unit))
|
.Select(unit => new HoldOrder(unit.Power, unit))
|
||||||
|
@ -308,9 +308,9 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
Dictionary<MoveOrder, DoesMove> moves = decisions
|
Dictionary<MoveOrder, DoesMove> moves = decisions
|
||||||
.OfType<DoesMove>()
|
.OfType<DoesMove>()
|
||||||
.ToDictionary(dm => dm.Order);
|
.ToDictionary(dm => dm.Order);
|
||||||
Dictionary<Unit, IsDislodged> dislodges = decisions
|
Dictionary<string, IsDislodged> dislodges = decisions
|
||||||
.OfType<IsDislodged>()
|
.OfType<IsDislodged>()
|
||||||
.ToDictionary(dm => dm.Order.Unit);
|
.ToDictionary(dm => dm.Order.Unit.Key);
|
||||||
|
|
||||||
// All moves to a particular season in a single phase result in the same future. Keep a
|
// All moves to a particular season in a single phase result in the same future. Keep a
|
||||||
// record of when a future season has been created.
|
// record of when a future season has been created.
|
||||||
|
@ -395,7 +395,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
OrderHistory history = newHistory[unitOrder.Unit.Season.Key];
|
OrderHistory history = newHistory[unitOrder.Unit.Season.Key];
|
||||||
// TODO does this add every order to every season??
|
// TODO does this add every order to every season??
|
||||||
history.Orders.Add(unitOrder);
|
history.Orders.Add(unitOrder);
|
||||||
history.IsDislodgedOutcomes[unitOrder.Unit.Key] = dislodges[unitOrder.Unit].Outcome == true;
|
history.IsDislodgedOutcomes[unitOrder.Unit.Key] = dislodges[unitOrder.Unit.Key].Outcome == true;
|
||||||
if (unitOrder is MoveOrder moveOrder)
|
if (unitOrder is MoveOrder moveOrder)
|
||||||
{
|
{
|
||||||
history.DoesMoveOutcomes[moveOrder.Unit.Key] = moves[moveOrder].Outcome == true;
|
history.DoesMoveOutcomes[moveOrder.Unit.Key] = moves[moveOrder].Outcome == true;
|
||||||
|
@ -520,7 +520,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
foreach (UnitOrder order in decision.Orders)
|
foreach (UnitOrder order in decision.Orders)
|
||||||
{
|
{
|
||||||
// TODO these aren't timeline-specific
|
// TODO these aren't timeline-specific
|
||||||
IsDislodged dislodged = decisions.IsDislodged[order.Unit];
|
IsDislodged dislodged = decisions.IsDislodged[order.Unit.Key];
|
||||||
progress |= ResolveDecision(dislodged, world, decisions, depth + 1);
|
progress |= ResolveDecision(dislodged, world, decisions, depth + 1);
|
||||||
if (history.IsDislodgedOutcomes.TryGetValue(order.Unit.Key, out bool previous)
|
if (history.IsDislodgedOutcomes.TryGetValue(order.Unit.Key, out bool previous)
|
||||||
&& dislodged.Resolved
|
&& dislodged.Resolved
|
||||||
|
@ -694,7 +694,7 @@ public class MovementPhaseAdjudicator : IPhaseAdjudicator
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support is also cut if the unit is dislodged.
|
// Support is also cut if the unit is dislodged.
|
||||||
IsDislodged dislodge = decisions.IsDislodged[decision.Order.Unit];
|
IsDislodged dislodge = decisions.IsDislodged[decision.Order.Unit.Key];
|
||||||
progress |= ResolveDecision(dislodge, world, decisions, depth + 1);
|
progress |= ResolveDecision(dislodge, world, decisions, depth + 1);
|
||||||
if (dislodge.Outcome == true)
|
if (dislodge.Outcome == true)
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,19 +12,23 @@ public static class PathFinder
|
||||||
/// Determines if a convoy path exists for a move in a convoy order.
|
/// Determines if a convoy path exists for a move in a convoy order.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool ConvoyPathExists(World world, ConvoyOrder order)
|
public static bool ConvoyPathExists(World world, ConvoyOrder order)
|
||||||
=> ConvoyPathExists(world, order.Target, order.Location, order.Season);
|
=> ConvoyPathExists(world, world.Map.GetLocation(order.Target), order.Location, order.Season);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if a convoy path exists for a move order.
|
/// Determines if a convoy path exists for a move order.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool ConvoyPathExists(World world, MoveOrder order)
|
public static bool ConvoyPathExists(World world, MoveOrder order)
|
||||||
=> ConvoyPathExists(world, order.Unit, world.Map.GetLocation(order.Location), order.Season);
|
=> ConvoyPathExists(
|
||||||
|
world,
|
||||||
|
world.Map.GetLocation(order.Unit),
|
||||||
|
world.Map.GetLocation(order.Location),
|
||||||
|
order.Season);
|
||||||
|
|
||||||
private static bool ConvoyPathExists(
|
private static bool ConvoyPathExists(
|
||||||
World world,
|
World world,
|
||||||
Unit movingUnit,
|
|
||||||
Location unitLocation,
|
Location unitLocation,
|
||||||
Season unitSeason)
|
Location destLocation,
|
||||||
|
Season destSeason)
|
||||||
{
|
{
|
||||||
// A convoy path exists between two locations if both are land locations in provinces that
|
// A convoy path exists between two locations if both are land locations in provinces that
|
||||||
// also have coasts, and between those coasts there is a path of adjacent sea provinces
|
// also have coasts, and between those coasts there is a path of adjacent sea provinces
|
||||||
|
@ -35,14 +39,14 @@ public static class PathFinder
|
||||||
.ToDictionary(unit => (unit.Location, unit.Season));
|
.ToDictionary(unit => (unit.Location, unit.Season));
|
||||||
|
|
||||||
// Verify that the origin is a coastal province.
|
// Verify that the origin is a coastal province.
|
||||||
if (world.Map.GetLocation(movingUnit).Type != LocationType.Land) return false;
|
if (unitLocation.Type != LocationType.Land) return false;
|
||||||
IEnumerable<Location> originCoasts = world.Map.GetLocation(movingUnit).Province.Locations
|
IEnumerable<Location> originCoasts = unitLocation.Province.Locations
|
||||||
.Where(location => location.Type == LocationType.Water);
|
.Where(location => location.Type == LocationType.Water);
|
||||||
if (!originCoasts.Any()) return false;
|
if (!originCoasts.Any()) return false;
|
||||||
|
|
||||||
// Verify that the destination is a coastal province.
|
// Verify that the destination is a coastal province.
|
||||||
if (unitLocation.Type != LocationType.Land) return false;
|
if (destLocation.Type != LocationType.Land) return false;
|
||||||
IEnumerable<Location> destCoasts = unitLocation.Province.Locations
|
IEnumerable<Location> destCoasts = destLocation.Province.Locations
|
||||||
.Where(location => location.Type == LocationType.Water);
|
.Where(location => location.Type == LocationType.Water);
|
||||||
if (!destCoasts.Any()) return false;
|
if (!destCoasts.Any()) return false;
|
||||||
|
|
||||||
|
@ -50,7 +54,7 @@ public static class PathFinder
|
||||||
// locations added to the to-visit set, but the logic will still work with these as
|
// locations added to the to-visit set, but the logic will still work with these as
|
||||||
// starting points.
|
// starting points.
|
||||||
Queue<(Location location, Season season)> toVisit = new(
|
Queue<(Location location, Season season)> toVisit = new(
|
||||||
originCoasts.Select(location => (location, unitSeason)));
|
originCoasts.Select(location => (location, destSeason)));
|
||||||
HashSet<(Location, Season)> visited = new();
|
HashSet<(Location, Season)> visited = new();
|
||||||
|
|
||||||
// Begin pathfinding.
|
// Begin pathfinding.
|
||||||
|
@ -64,7 +68,7 @@ public static class PathFinder
|
||||||
foreach ((Location adjLocation, Season adjSeason) in adjacents)
|
foreach ((Location adjLocation, Season adjSeason) in adjacents)
|
||||||
{
|
{
|
||||||
// If the destination is adjacent, then a path exists.
|
// If the destination is adjacent, then a path exists.
|
||||||
if (destCoasts.Contains(adjLocation) && unitSeason == adjSeason) return true;
|
if (destCoasts.Contains(adjLocation) && destSeason == adjSeason) return true;
|
||||||
|
|
||||||
// If not, add this location to the to-visit set if it isn't a coast, has a fleet,
|
// If not, add this location to the to-visit set if it isn't a coast, has a fleet,
|
||||||
// and hasn't already been visited.
|
// and hasn't already been visited.
|
||||||
|
|
|
@ -37,6 +37,6 @@ public class ConvoyOrder : UnitOrder
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{this.Unit} C {this.Target} -> {(this.Province, this.Season).ToShort()}";
|
return $"{this.Unit} con {this.Target} -> {(this.Province, this.Season).ToShort()}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,6 @@ public class SupportHoldOrder : SupportOrder
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{this.Unit} S {this.Target}";
|
return $"{this.Unit} sup {this.Target}";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -36,6 +36,6 @@ public class SupportMoveOrder : SupportOrder
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{this.Unit} S {this.Target} -> {(this.Province, this.Season).ToShort()}";
|
return $"{this.Unit} sup {this.Target} -> {(this.Province, this.Season).ToShort()}";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -101,7 +101,7 @@ public class TimeTravelTest
|
||||||
world.RetreatingUnits.Count,
|
world.RetreatingUnits.Count,
|
||||||
Is.EqualTo(1),
|
Is.EqualTo(1),
|
||||||
"Expected A Tyr a0 to be in retreat");
|
"Expected A Tyr a0 to be in retreat");
|
||||||
Assert.That(world.RetreatingUnits.First().Unit, Is.EqualTo(tyr0.Order.Unit));
|
Assert.That(world.RetreatingUnits.First().Unit.Key, Is.EqualTo(tyr0.Order.Unit.Key));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
@ -206,7 +206,7 @@ public class MovementAdjudicatorTest
|
||||||
// Confirm the unit was created in the future
|
// Confirm the unit was created in the future
|
||||||
Unit u2 = updated.GetUnitAt("Mun", s2);
|
Unit u2 = updated.GetUnitAt("Mun", s2);
|
||||||
Assert.That(updated.Units.Count, Is.EqualTo(2));
|
Assert.That(updated.Units.Count, Is.EqualTo(2));
|
||||||
Assert.That(u2, Is.Not.EqualTo(mun1.Order.Unit));
|
Assert.That(u2.Key, Is.Not.EqualTo(mun1.Order.Unit.Key));
|
||||||
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit.Key));
|
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit.Key));
|
||||||
Assert.That(u2.Season, Is.EqualTo(s2));
|
Assert.That(u2.Season, Is.EqualTo(s2));
|
||||||
|
|
||||||
|
@ -256,7 +256,7 @@ public class MovementAdjudicatorTest
|
||||||
// Confirm the unit was created in the future
|
// Confirm the unit was created in the future
|
||||||
Unit u2 = updated.GetUnitAt("Tyr", s2);
|
Unit u2 = updated.GetUnitAt("Tyr", s2);
|
||||||
Assert.That(updated.Units.Count, Is.EqualTo(2));
|
Assert.That(updated.Units.Count, Is.EqualTo(2));
|
||||||
Assert.That(u2, Is.Not.EqualTo(mun1.Order.Unit));
|
Assert.That(u2.Key, Is.Not.EqualTo(mun1.Order.Unit.Key));
|
||||||
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit.Key));
|
Assert.That(u2.Past, Is.EqualTo(mun1.Order.Unit.Key));
|
||||||
Assert.That(u2.Season, Is.EqualTo(s2));
|
Assert.That(u2.Season, Is.EqualTo(s2));
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ public abstract class OrderReference
|
||||||
if (this.Order is UnitOrder unitOrder)
|
if (this.Order is UnitOrder unitOrder)
|
||||||
{
|
{
|
||||||
var replacementOrder = this.Builder.ValidationResults.Where(
|
var replacementOrder = this.Builder.ValidationResults.Where(
|
||||||
v => v.Order is UnitOrder uo && uo != unitOrder && uo.Unit == unitOrder.Unit);
|
v => v.Order is UnitOrder uo && uo != unitOrder && uo.Unit.Key == unitOrder.Unit.Key);
|
||||||
if (replacementOrder.Any())
|
if (replacementOrder.Any())
|
||||||
{
|
{
|
||||||
return replacementOrder.Single();
|
return replacementOrder.Single();
|
||||||
|
@ -142,7 +142,7 @@ public abstract class OrderReference
|
||||||
if (this.Order is UnitOrder unitOrder)
|
if (this.Order is UnitOrder unitOrder)
|
||||||
{
|
{
|
||||||
var retreat = this.Builder.World.RetreatingUnits.Where(
|
var retreat = this.Builder.World.RetreatingUnits.Where(
|
||||||
ru => ru.Unit == unitOrder.Unit);
|
ru => ru.Unit.Key == unitOrder.Unit.Key);
|
||||||
if (retreat.Any())
|
if (retreat.Any())
|
||||||
{
|
{
|
||||||
return retreat.Single();
|
return retreat.Single();
|
||||||
|
|
|
@ -94,11 +94,8 @@ public class SerializationTest
|
||||||
Assert.That(setup.World.OrderHistory[s0.Key].DoesMoveOutcomes.Count, Is.GreaterThan(0), "Missing moves");
|
Assert.That(setup.World.OrderHistory[s0.Key].DoesMoveOutcomes.Count, Is.GreaterThan(0), "Missing moves");
|
||||||
Assert.That(setup.World.OrderHistory[s0.Key].IsDislodgedOutcomes.Count, Is.GreaterThan(0), "Missing dislodges");
|
Assert.That(setup.World.OrderHistory[s0.Key].IsDislodgedOutcomes.Count, Is.GreaterThan(0), "Missing dislodges");
|
||||||
|
|
||||||
// Assert.Ignore("Serialization doesn't fully work yet");
|
|
||||||
|
|
||||||
// Serialize and deserialize the world
|
// Serialize and deserialize the world
|
||||||
string serialized = JsonSerializer.Serialize(setup.World, Options);
|
string serialized = JsonSerializer.Serialize(setup.World, Options);
|
||||||
Console.WriteLine(serialized);
|
|
||||||
World reserialized = JsonSerializer.Deserialize<World>(serialized, Options)
|
World reserialized = JsonSerializer.Deserialize<World>(serialized, Options)
|
||||||
?? throw new AssertionException("Failed to reserialize world");
|
?? throw new AssertionException("Failed to reserialize world");
|
||||||
|
|
||||||
|
@ -120,16 +117,16 @@ public class SerializationTest
|
||||||
setup.ValidateOrders();
|
setup.ValidateOrders();
|
||||||
Assert.That(mun1, Is.Valid);
|
Assert.That(mun1, Is.Valid);
|
||||||
var adjudications = setup.AdjudicateOrders();
|
var adjudications = setup.AdjudicateOrders();
|
||||||
foreach (var adj in adjudications)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"{adj}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.That(mun1, Is.NotCut);
|
Assert.That(mun1, Is.NotCut);
|
||||||
|
|
||||||
|
AttackStrength mun0attack = adjudications.OfType<AttackStrength>().Single();
|
||||||
|
Assert.That(mun0attack.Supports, Is.Not.Empty, "Support not tracked");
|
||||||
|
|
||||||
DoesMove mun0move = adjudications.OfType<DoesMove>().Single(move => move.Order.Unit.Key == mun0.Order.Unit.Key);
|
DoesMove mun0move = adjudications.OfType<DoesMove>().Single(move => move.Order.Unit.Key == mun0.Order.Unit.Key);
|
||||||
Assert.That(mun0move.Outcome, Is.True);
|
Assert.That(mun0move.Outcome, Is.True);
|
||||||
|
|
||||||
IsDislodged tyr0dislodge = adjudications.OfType<IsDislodged>().Single(dis => dis.Order.Unit.Key == tyr0.Order.Unit.Key);
|
IsDislodged tyr0dislodge = adjudications.OfType<IsDislodged>().Single(dis => dis.Order.Unit.Key == tyr0.Order.Unit.Key);
|
||||||
Assert.That(tyr0dislodge, Is.True);
|
Assert.That(tyr0dislodge.Outcome, Is.True);
|
||||||
|
|
||||||
// Confirm that an alternate future is created.
|
// Confirm that an alternate future is created.
|
||||||
World world = setup.UpdateWorld();
|
World world = setup.UpdateWorld();
|
||||||
|
@ -143,6 +140,6 @@ public class SerializationTest
|
||||||
world.RetreatingUnits.Count,
|
world.RetreatingUnits.Count,
|
||||||
Is.EqualTo(1),
|
Is.EqualTo(1),
|
||||||
"Expected A Tyr a0 to be in retreat");
|
"Expected A Tyr a0 to be in retreat");
|
||||||
Assert.That(world.RetreatingUnits.First().Unit, Is.EqualTo(tyr0.Order.Unit));
|
Assert.That(world.RetreatingUnits.First().Unit.Key, Is.EqualTo(tyr0.Order.Unit.Key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue