From c89c3a0947da106be6c55ade896d7c49e96b2e0b Mon Sep 17 00:00:00 2001 From: hz Date: Wed, 20 May 2026 21:10:19 -0500 Subject: [PATCH] territory claiming modification --- .../scenes/game/hex_grid_screen.gd | 5 +- .../Controllers/LocationsController.cs | 9 ++- .../LocationsApi/Services/LocationService.cs | 28 ++++++++- .../Services/UnitTrainingService.cs | 62 ------------------- 4 files changed, 35 insertions(+), 69 deletions(-) diff --git a/game/territory-autobattler/scenes/game/hex_grid_screen.gd b/game/territory-autobattler/scenes/game/hex_grid_screen.gd index 085a795..9cf3791 100644 --- a/game/territory-autobattler/scenes/game/hex_grid_screen.gd +++ b/game/territory-autobattler/scenes/game/hex_grid_screen.gd @@ -677,7 +677,7 @@ func _confirm_scout_claim() -> void: if pending_move_q == 9999: return if not _can_claim_tile(pending_move_q, pending_move_r): - coordinate_label.text = "Scouts can only claim visible unclaimed territory adjacent to controlled territory." + coordinate_label.text = "Scouts can only claim visible neutral or enemy territory adjacent to controlled territory." return selected_q = pending_move_q selected_r = pending_move_r @@ -804,8 +804,9 @@ func _can_claim_tile(q: int, r: int) -> bool: if location.is_empty(): return false + var username: String = str(session.get("username", "")) var owner_username: String = _nullable_string(location.get("ownerUsername", "")) - return owner_username.is_empty() and grid_view.has_owned_neighbor(q, r) + return owner_username != username and grid_view.has_owned_neighbor(q, r) func _update_action_buttons() -> void: diff --git a/microservices/LocationsApi/Controllers/LocationsController.cs b/microservices/LocationsApi/Controllers/LocationsController.cs index b3a9bd2..d57eba1 100644 --- a/microservices/LocationsApi/Controllers/LocationsController.cs +++ b/microservices/LocationsApi/Controllers/LocationsController.cs @@ -100,19 +100,22 @@ public class LocationsController : ControllerBase { return Ok(LocationResponse.FromLocation(existingLocation)); } + } - return Conflict("Location is already controlled by another player."); + if (!User.IsInRole("SUPER") && !await _locations.CanScoutClaimAsync(username, request.Q, request.R)) + { + return BadRequest("Scouts can only claim neutral territory or empty enemy territory without units or buildings."); } if (!User.IsInRole("SUPER") && !await _units.TryConsumeCompletedScoutAsync(username)) { - return BadRequest("A completed scout is required to claim unclaimed territory."); + return BadRequest("A completed scout is required to claim territory."); } var location = await _locations.ClaimAsync(request.Q, request.R, username); if (location is null) { - return Conflict("Location is already controlled by another player."); + return Conflict("Location cannot be claimed while occupied or built up."); } return Ok(LocationResponse.FromLocation(location)); diff --git a/microservices/LocationsApi/Services/LocationService.cs b/microservices/LocationsApi/Services/LocationService.cs index f2f77d5..e5b4c53 100644 --- a/microservices/LocationsApi/Services/LocationService.cs +++ b/microservices/LocationsApi/Services/LocationService.cs @@ -145,8 +145,12 @@ public class LocationService var existing = await GetAsync(q, r); if (existing is not null) { - if (!string.IsNullOrWhiteSpace(existing.OwnerUsername) && - !string.Equals(existing.OwnerUsername, username, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(existing.OwnerUsername, username, StringComparison.OrdinalIgnoreCase)) + { + return existing; + } + + if (!await CanScoutClaimAsync(username, q, r)) { return null; } @@ -164,6 +168,26 @@ public class LocationService return await UpsertAsync(new UpsertLocationRequest { Q = q, R = r, Terrain = "Field" }, username); } + public async Task CanScoutClaimAsync(string username, int q, int r) + { + var existing = await GetAsync(q, r); + if (existing is not null) + { + if (string.Equals(existing.OwnerUsername, username, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (string.Equals(existing.Kind, "Home", StringComparison.OrdinalIgnoreCase) || + !string.IsNullOrWhiteSpace(existing.BuildingType)) + { + return false; + } + } + + return !await _units.Find(x => x.Q == q && x.R == r).AnyAsync(); + } + public async Task BuildAsync(BuildLocationRequest request, string username) { var buildingType = NormalizeBuildingType(request.BuildingType); diff --git a/microservices/LocationsApi/Services/UnitTrainingService.cs b/microservices/LocationsApi/Services/UnitTrainingService.cs index 259aab2..fd198bf 100644 --- a/microservices/LocationsApi/Services/UnitTrainingService.cs +++ b/microservices/LocationsApi/Services/UnitTrainingService.cs @@ -375,10 +375,6 @@ public class UnitTrainingService .Unset(x => x.MovementCompletesAtUtc) .Set(x => x.UpdatedAtUtc, now)); - if (unit.UnitType == ArcherUnitType) - { - await ClaimTargetIfEmptyAsync(username, destinationQ, destinationR, now); - } } } @@ -503,64 +499,6 @@ public class UnitTrainingService return step == start ? target : step; } - 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(); - var target = await _locations.Find(x => x.Q == q && x.R == r).FirstOrDefaultAsync(); - var hasBuilding = target is not null && - (!string.IsNullOrWhiteSpace(target.BuildingType) || - string.Equals(target.Kind, "Home", StringComparison.OrdinalIgnoreCase)); - - if (hasOtherUnits || hasBuilding) - { - return; - } - - if (target is null) - { - var location = new Location - { - Q = q, - R = r, - Terrain = "Field", - Kind = "Claimed", - OwnerUsername = username, - Level = 1, - CreatedAtUtc = now, - UpdatedAtUtc = now - }; - - try - { - await _locations.InsertOneAsync(location); - } - catch (MongoWriteException) - { - } - - return; - } - - if (string.Equals(target.OwnerUsername, username, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - var capturableFilter = Builders.Filter.And( - Builders.Filter.Eq(x => x.Id, target.Id), - Builders.Filter.Ne(x => x.Kind, "Home"), - Builders.Filter.Or( - Builders.Filter.Eq(x => x.BuildingType, null), - Builders.Filter.Eq(x => x.BuildingType, ""))); - - await _locations.UpdateOneAsync( - capturableFilter, - Builders.Update - .Set(x => x.OwnerUsername, username) - .Set(x => x.Kind, "Claimed") - .Set(x => x.UpdatedAtUtc, now)); - } - private static int HexDistance(int q1, int r1, int q2, int r2) { var s1 = -q1 - r1;