allowing territory to be abandoned, units to be dismissed, and buildings to be destroyed
This commit is contained in:
@@ -180,6 +180,52 @@ public class LocationsController : ControllerBase
|
||||
: Ok(LocationResponse.FromLocation(location));
|
||||
}
|
||||
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
[HttpPost("destroy-building")]
|
||||
public async Task<ActionResult<LocationResponse>> DestroyBuilding([FromBody] TerritoryLocationRequest request)
|
||||
{
|
||||
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var location = await _locations.DestroyBuildingAsync(username, request.Q, request.R);
|
||||
return location is null
|
||||
? BadRequest("Select controlled territory with a non-town-hall building.")
|
||||
: Ok(LocationResponse.FromLocation(location));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
[HttpPost("abandon")]
|
||||
public async Task<ActionResult<LocationResponse>> Abandon([FromBody] TerritoryLocationRequest request)
|
||||
{
|
||||
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var location = await _locations.AbandonAsync(username, request.Q, request.R);
|
||||
return location is null
|
||||
? BadRequest("Select controlled non-home territory to give up.")
|
||||
: Ok(LocationResponse.FromLocation(location));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
[HttpGet("home")]
|
||||
public async Task<ActionResult<LocationResponse>> GetHome()
|
||||
|
||||
@@ -89,4 +89,34 @@ public class UnitsController : ControllerBase
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
[HttpPost("dismiss")]
|
||||
public async Task<ActionResult<DismissUnitSquadResponse>> Dismiss([FromBody] DismissUnitSquadRequest request)
|
||||
{
|
||||
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var dismissedCount = await _units.DismissSquadAsync(username, request);
|
||||
if (dismissedCount == 0)
|
||||
{
|
||||
return BadRequest(new { error = "No matching idle units are available to dismiss." });
|
||||
}
|
||||
|
||||
return Ok(new DismissUnitSquadResponse
|
||||
{
|
||||
UnitType = request.UnitType,
|
||||
DismissedCount = dismissedCount
|
||||
});
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ public class DeassignWorkerRequest
|
||||
public int R { get; set; }
|
||||
}
|
||||
|
||||
public class TerritoryLocationRequest
|
||||
{
|
||||
public int Q { get; set; }
|
||||
public int R { get; set; }
|
||||
}
|
||||
|
||||
public class LocationResponse
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
@@ -17,6 +17,19 @@ public class MoveUnitSquadRequest
|
||||
public int ToR { get; set; }
|
||||
}
|
||||
|
||||
public class DismissUnitSquadRequest
|
||||
{
|
||||
public string UnitType { get; set; } = "Archer";
|
||||
public int Q { get; set; }
|
||||
public int R { get; set; }
|
||||
}
|
||||
|
||||
public class DismissUnitSquadResponse
|
||||
{
|
||||
public string UnitType { get; set; } = "";
|
||||
public int DismissedCount { get; set; }
|
||||
}
|
||||
|
||||
public class UnitTrainingResponse
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user