allowing territory to be abandoned, units to be dismissed, and buildings to be destroyed

This commit is contained in:
hz
2026-05-20 19:38:33 -05:00
parent 4a3d3af41c
commit f142440aca
7 changed files with 445 additions and 16 deletions
@@ -355,6 +355,93 @@ public class LocationService
return updated;
}
public async Task<Location?> DestroyBuildingAsync(string username, int q, int r)
{
var now = DateTime.UtcNow;
var location = await GetAsync(q, r);
if (location is null ||
!string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase) ||
string.IsNullOrWhiteSpace(location.BuildingType))
{
return null;
}
if (string.Equals(location.Kind, "Home", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The town hall cannot be destroyed.");
}
if (!string.IsNullOrWhiteSpace(location.WorkerUnitId))
{
await _units.UpdateOneAsync(
x => x.Id == location.WorkerUnitId && x.Username == username,
Builders<UnitInstance>.Update
.Set(x => x.Q, q)
.Set(x => x.R, r)
.Set(x => x.Status, "Idle")
.Set(x => x.MovementRemaining, 1)
.Set(x => x.UpdatedAtUtc, now));
}
var updated = await _locations.FindOneAndUpdateAsync(
x => x.Id == location.Id && x.OwnerUsername == username && x.BuildingType != null && x.BuildingType != "",
Builders<Location>.Update
.Unset(x => x.BuildingType)
.Unset(x => x.BuildingStatus)
.Unset(x => x.BuildingStartedAtUtc)
.Unset(x => x.BuildingCompletesAtUtc)
.Unset(x => x.BuildingBuiltAtUtc)
.Unset(x => x.WorkerUnitId)
.Unset(x => x.WorkerUnitType)
.Set(x => x.StoredResourceAmount, 0)
.Set(x => x.StoredResourceCapacity, 10)
.Unset(x => x.LastResourceGeneratedAtUtc)
.Set(x => x.UpdatedAtUtc, now),
new FindOneAndUpdateOptions<Location> { ReturnDocument = ReturnDocument.After });
await RecalculateWorkerGenerationAsync(username);
return updated;
}
public async Task<Location?> AbandonAsync(string username, int q, int r)
{
var location = await GetAsync(q, r);
if (location is null ||
!string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase))
{
return null;
}
if (string.Equals(location.Kind, "Home", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The town hall cannot be given up.");
}
if (!string.IsNullOrWhiteSpace(location.BuildingType) || !string.IsNullOrWhiteSpace(location.WorkerUnitId))
{
throw new InvalidOperationException("Destroy the building before giving up this territory.");
}
var hasUnits = await _units.Find(x =>
x.Username == username &&
x.Q == q &&
x.R == r)
.AnyAsync();
if (hasUnits)
{
throw new InvalidOperationException("Move or dismiss units on this tile before giving up the territory.");
}
return await _locations.FindOneAndUpdateAsync(
x => x.Id == location.Id && x.OwnerUsername == username,
Builders<Location>.Update
.Set(x => x.Kind, "Open")
.Unset(x => x.OwnerUsername)
.Unset(x => x.Label)
.Set(x => x.UpdatedAtUtc, DateTime.UtcNow),
new FindOneAndUpdateOptions<Location> { ReturnDocument = ReturnDocument.After });
}
private async Task RecalculateWorkerGenerationAsync(string username)
{
var built = await _locations.Find(x =>
@@ -197,9 +197,9 @@ public class UnitTrainingService
}
var movementDistance = HexDistance(request.FromQ, request.FromR, request.ToQ, request.ToR);
if (normalizedUnitType == ArcherUnitType && !await CanMoveToAsync(username, request.ToQ, request.ToR))
if ((normalizedUnitType == ArcherUnitType || normalizedUnitType == ScoutUnitType) && !await CanMoveToAsync(username, request.ToQ, request.ToR))
{
throw new InvalidOperationException("Archers can only move to visible tiles.");
throw new InvalidOperationException($"{normalizedUnitType}s can only move to visible tiles.");
}
if (normalizedUnitType == PeasantUnitType && !await CanPeasantMoveToAsync(username, request.ToQ, request.ToR))
@@ -257,6 +257,26 @@ public class UnitTrainingService
.ToListAsync();
}
public async Task<int> DismissSquadAsync(string username, DismissUnitSquadRequest request)
{
var normalizedUnitType = NormalizeUnitType(request.UnitType);
if (normalizedUnitType != ScoutUnitType && normalizedUnitType != ArcherUnitType && normalizedUnitType != PeasantUnitType)
{
throw new ArgumentException("Only Scouts, Archers, and Peasants can be dismissed right now.", nameof(request.UnitType));
}
await CompletePendingMovementAsync(username);
var result = await _units.DeleteManyAsync(x =>
x.Username == username &&
x.UnitType == normalizedUnitType &&
x.Status == "Idle" &&
x.Q == request.Q &&
x.R == request.R);
return (int)result.DeletedCount;
}
private async Task<Location?> GetBuiltBarracksAsync(string username, int q, int r)
{
await CompleteFinishedBuildingsAsync(username);