Movement enhancements
This commit is contained in:
@@ -9,6 +9,7 @@ public class TrainUnitRequest
|
||||
|
||||
public class MoveUnitSquadRequest
|
||||
{
|
||||
public string? UnitId { get; set; }
|
||||
public string UnitType { get; set; } = "Archer";
|
||||
public int FromQ { get; set; }
|
||||
public int FromR { get; set; }
|
||||
|
||||
@@ -177,9 +177,9 @@ public class UnitTrainingService
|
||||
await RefillMovementAsync(username);
|
||||
|
||||
var normalizedUnitType = NormalizeUnitType(request.UnitType);
|
||||
if (normalizedUnitType != ArcherUnitType)
|
||||
if (normalizedUnitType != ArcherUnitType && normalizedUnitType != PeasantUnitType)
|
||||
{
|
||||
throw new ArgumentException("Only Archer squads can be moved right now.", nameof(request.UnitType));
|
||||
throw new ArgumentException("Only Archers and Peasants can be moved right now.", nameof(request.UnitType));
|
||||
}
|
||||
|
||||
if (request.FromQ == request.ToQ && request.FromR == request.ToR)
|
||||
@@ -187,24 +187,38 @@ public class UnitTrainingService
|
||||
throw new InvalidOperationException("Select a different destination tile.");
|
||||
}
|
||||
|
||||
if (HexDistance(request.FromQ, request.FromR, request.ToQ, request.ToR) != 1)
|
||||
var movementDistance = HexDistance(request.FromQ, request.FromR, request.ToQ, request.ToR);
|
||||
if (normalizedUnitType == ArcherUnitType && movementDistance != 1)
|
||||
{
|
||||
throw new InvalidOperationException("Archers can only move to adjacent tiles.");
|
||||
}
|
||||
|
||||
if (!await CanMoveToAsync(username, request.ToQ, request.ToR))
|
||||
if (normalizedUnitType == ArcherUnitType && !await CanMoveToAsync(username, request.ToQ, request.ToR))
|
||||
{
|
||||
throw new InvalidOperationException("Archers can only move to visible tiles.");
|
||||
}
|
||||
|
||||
if (normalizedUnitType == PeasantUnitType && !await CanPeasantMoveToAsync(username, request.ToQ, request.ToR))
|
||||
{
|
||||
throw new InvalidOperationException("Peasants can only move within your controlled territory.");
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var units = await _units.Find(x =>
|
||||
x.Username == username &&
|
||||
x.UnitType == ArcherUnitType &&
|
||||
x.Status == "Idle" &&
|
||||
x.Q == request.FromQ &&
|
||||
x.R == request.FromR)
|
||||
.ToListAsync();
|
||||
var unitFilter = Builders<UnitInstance>.Filter.And(
|
||||
Builders<UnitInstance>.Filter.Eq(x => x.Username, username),
|
||||
Builders<UnitInstance>.Filter.Eq(x => x.UnitType, normalizedUnitType),
|
||||
Builders<UnitInstance>.Filter.Eq(x => x.Status, "Idle"),
|
||||
Builders<UnitInstance>.Filter.Eq(x => x.Q, request.FromQ),
|
||||
Builders<UnitInstance>.Filter.Eq(x => x.R, request.FromR));
|
||||
if (normalizedUnitType == PeasantUnitType)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(request.UnitId))
|
||||
{
|
||||
unitFilter &= Builders<UnitInstance>.Filter.Eq(x => x.Id, request.UnitId);
|
||||
}
|
||||
}
|
||||
|
||||
var units = await _units.Find(unitFilter).ToListAsync();
|
||||
|
||||
if (units.Count == 0)
|
||||
{
|
||||
@@ -216,13 +230,9 @@ public class UnitTrainingService
|
||||
throw new InvalidOperationException("That squad has no movement remaining this tick.");
|
||||
}
|
||||
|
||||
var movementCompletesAt = now.AddMinutes(1);
|
||||
var movementCompletesAt = now.AddMinutes(Math.Max(1, movementDistance));
|
||||
await _units.UpdateManyAsync(
|
||||
x => x.Username == username &&
|
||||
x.UnitType == ArcherUnitType &&
|
||||
x.Status == "Idle" &&
|
||||
x.Q == request.FromQ &&
|
||||
x.R == request.FromR,
|
||||
unitFilter,
|
||||
Builders<UnitInstance>.Update
|
||||
.Set(x => x.Status, "Moving")
|
||||
.Inc(x => x.MovementRemaining, -1)
|
||||
@@ -234,7 +244,7 @@ public class UnitTrainingService
|
||||
|
||||
return await _units.Find(x =>
|
||||
x.Username == username &&
|
||||
x.UnitType == ArcherUnitType &&
|
||||
x.UnitType == normalizedUnitType &&
|
||||
x.Status == "Moving" &&
|
||||
x.Q == request.FromQ &&
|
||||
x.R == request.FromR &&
|
||||
@@ -282,11 +292,34 @@ public class UnitTrainingService
|
||||
continue;
|
||||
}
|
||||
|
||||
var destinationQ = unit.DestinationQ.Value;
|
||||
var destinationR = unit.DestinationR.Value;
|
||||
var currentQ = unit.Q;
|
||||
var currentR = unit.R;
|
||||
var nextTick = unit.MovementCompletesAtUtc!.Value;
|
||||
while (nextTick <= now && HexDistance(currentQ, currentR, destinationQ, destinationR) > 1)
|
||||
{
|
||||
(currentQ, currentR) = NextStepToward(currentQ, currentR, destinationQ, destinationR);
|
||||
nextTick = nextTick.AddMinutes(1);
|
||||
}
|
||||
|
||||
if (HexDistance(currentQ, currentR, destinationQ, destinationR) > 1)
|
||||
{
|
||||
await _units.UpdateOneAsync(
|
||||
x => x.Id == unit.Id,
|
||||
Builders<UnitInstance>.Update
|
||||
.Set(x => x.Q, currentQ)
|
||||
.Set(x => x.R, currentR)
|
||||
.Set(x => x.MovementCompletesAtUtc, nextTick)
|
||||
.Set(x => x.UpdatedAtUtc, now));
|
||||
continue;
|
||||
}
|
||||
|
||||
await _units.UpdateOneAsync(
|
||||
x => x.Id == unit.Id,
|
||||
Builders<UnitInstance>.Update
|
||||
.Set(x => x.Q, unit.DestinationQ.Value)
|
||||
.Set(x => x.R, unit.DestinationR.Value)
|
||||
.Set(x => x.Q, destinationQ)
|
||||
.Set(x => x.R, destinationR)
|
||||
.Set(x => x.Status, "Idle")
|
||||
.Set(x => x.MovementPerTick, movementPerTick)
|
||||
.Set(x => x.MovementRemaining, movementPerTick)
|
||||
@@ -299,7 +332,7 @@ public class UnitTrainingService
|
||||
|
||||
if (unit.UnitType == ArcherUnitType)
|
||||
{
|
||||
await ClaimTargetIfEmptyAsync(username, unit.DestinationQ.Value, unit.DestinationR.Value, now);
|
||||
await ClaimTargetIfEmptyAsync(username, destinationQ, destinationR, now);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,6 +396,15 @@ public class UnitTrainingService
|
||||
return playerUnits.Any(unit => HexDistance(unit.Q, unit.R, q, r) <= 1);
|
||||
}
|
||||
|
||||
private async Task<bool> CanPeasantMoveToAsync(string username, int q, int r)
|
||||
{
|
||||
return await _locations.Find(x =>
|
||||
x.OwnerUsername == username &&
|
||||
x.Q == q &&
|
||||
x.R == r)
|
||||
.AnyAsync();
|
||||
}
|
||||
|
||||
private async Task ClaimTargetIfEmptyAsync(string username, int q, int r, DateTime now)
|
||||
{
|
||||
var hasOtherUnits = await _units.Find(x => x.Username != username && x.Q == q && x.R == r).AnyAsync();
|
||||
@@ -428,6 +470,25 @@ public class UnitTrainingService
|
||||
return Math.Max(Math.Max(Math.Abs(q1 - q2), Math.Abs(r1 - r2)), Math.Abs(s1 - s2));
|
||||
}
|
||||
|
||||
private static (int q, int r) NextStepToward(int fromQ, int fromR, int toQ, int toR)
|
||||
{
|
||||
return NeighborCoordinates(fromQ, fromR)
|
||||
.OrderBy(x => HexDistance(x.q, x.r, toQ, toR))
|
||||
.ThenBy(x => x.q)
|
||||
.ThenBy(x => x.r)
|
||||
.First();
|
||||
}
|
||||
|
||||
private static IEnumerable<(int q, int r)> NeighborCoordinates(int q, int r)
|
||||
{
|
||||
yield return (q + 1, r);
|
||||
yield return (q + 1, r - 1);
|
||||
yield return (q, r - 1);
|
||||
yield return (q - 1, r);
|
||||
yield return (q - 1, r + 1);
|
||||
yield return (q, r + 1);
|
||||
}
|
||||
|
||||
public async Task<bool> TryConsumeCompletedScoutAsync(string username)
|
||||
{
|
||||
await CompleteFinishedTrainingAsync(username);
|
||||
|
||||
Reference in New Issue
Block a user