updating unit movement
This commit is contained in:
@@ -6,6 +6,8 @@ namespace LocationsApi.Services;
|
||||
public class LocationService
|
||||
{
|
||||
private readonly IMongoCollection<Location> _locations;
|
||||
private readonly IMongoCollection<UnitInstance> _units;
|
||||
private readonly IMongoCollection<LocationIntel> _intel;
|
||||
private readonly ResourceService _resources;
|
||||
private readonly GameSettingsService _settings;
|
||||
private readonly int _homeMinDistance;
|
||||
@@ -27,6 +29,8 @@ public class LocationService
|
||||
var client = new MongoClient(connectionString);
|
||||
var database = client.GetDatabase(databaseName);
|
||||
_locations = database.GetCollection<Location>("Locations");
|
||||
_units = database.GetCollection<UnitInstance>("Units");
|
||||
_intel = database.GetCollection<LocationIntel>("LocationIntel");
|
||||
_resources = resources;
|
||||
_settings = settings;
|
||||
_homeMinDistance = int.TryParse(cfg["World:HomeMinDistance"], out var minDistance) ? Math.Max(1, minDistance) : 8;
|
||||
@@ -74,6 +78,13 @@ public class LocationService
|
||||
Unique = true,
|
||||
PartialFilterExpression = Builders<Location>.Filter.Eq(x => x.Kind, "Home")
|
||||
}));
|
||||
|
||||
_intel.Indexes.CreateOne(new CreateIndexModel<LocationIntel>(
|
||||
Builders<LocationIntel>.IndexKeys
|
||||
.Ascending(x => x.Username)
|
||||
.Ascending(x => x.Q)
|
||||
.Ascending(x => x.R),
|
||||
new CreateIndexOptions { Unique = true }));
|
||||
}
|
||||
|
||||
public Task<List<Location>> GetAllAsync() =>
|
||||
@@ -315,8 +326,10 @@ public class LocationService
|
||||
ownedLocations.Add(await AssignHomeAsync(username));
|
||||
}
|
||||
|
||||
var playerUnits = await _units.Find(x => x.Username == username).ToListAsync();
|
||||
var depth = Math.Max(0, visibilityDepth ?? _visibilityDepth);
|
||||
var visibleCoordinates = new HashSet<(int q, int r)>();
|
||||
var occupiedCoordinates = playerUnits.Select(x => (x.Q, x.R)).ToHashSet();
|
||||
|
||||
foreach (var owned in ownedLocations)
|
||||
{
|
||||
@@ -326,6 +339,14 @@ public class LocationService
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var unit in playerUnits)
|
||||
{
|
||||
foreach (var coordinate in HexRange(unit.Q, unit.R, 1))
|
||||
{
|
||||
visibleCoordinates.Add(coordinate);
|
||||
}
|
||||
}
|
||||
|
||||
var minQ = visibleCoordinates.Min(x => x.q);
|
||||
var maxQ = visibleCoordinates.Max(x => x.q);
|
||||
var minR = visibleCoordinates.Min(x => x.r);
|
||||
@@ -349,12 +370,15 @@ public class LocationService
|
||||
}
|
||||
}
|
||||
|
||||
await RefreshIntelAsync(username, occupiedCoordinates, existingByCoordinate);
|
||||
var intelByCoordinate = await GetIntelByCoordinateAsync(username, visibleCoordinates);
|
||||
|
||||
return visibleCoordinates
|
||||
.OrderBy(x => x.q)
|
||||
.ThenBy(x => x.r)
|
||||
.Select(coordinate =>
|
||||
existingByCoordinate.TryGetValue(coordinate, out var existing)
|
||||
? LocationResponse.FromLocation(existing)
|
||||
? VisibleResponseFor(username, existing, occupiedCoordinates, intelByCoordinate)
|
||||
: throw new InvalidOperationException($"Visible location {coordinate.q},{coordinate.r} was not generated."))
|
||||
.ToList();
|
||||
}
|
||||
@@ -369,7 +393,10 @@ public class LocationService
|
||||
var existing = await GetAsync(q, r);
|
||||
if (existing is not null)
|
||||
{
|
||||
return LocationResponse.FromLocation(existing);
|
||||
var occupiedCoordinates = await GetOccupiedCoordinatesAsync(username);
|
||||
await RefreshIntelAsync(username, occupiedCoordinates, new Dictionary<(int Q, int R), Location> { [(existing.Q, existing.R)] = existing });
|
||||
var intelByCoordinate = await GetIntelByCoordinateAsync(username, new[] { (q, r) });
|
||||
return VisibleResponseFor(username, existing, occupiedCoordinates, intelByCoordinate);
|
||||
}
|
||||
|
||||
var nearbyLocations = await GetRangeAsync(q - 1, q + 1, r - 1, r + 1);
|
||||
@@ -388,7 +415,13 @@ public class LocationService
|
||||
return false;
|
||||
}
|
||||
|
||||
return ownedLocations.Any(owned => HexDistance(owned.Q, owned.R, q, r) <= _visibilityDepth);
|
||||
if (ownedLocations.Any(owned => HexDistance(owned.Q, owned.R, q, r) <= _visibilityDepth))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var playerUnits = await _units.Find(x => x.Username == username).ToListAsync();
|
||||
return playerUnits.Any(unit => HexDistance(unit.Q, unit.R, q, r) <= 1);
|
||||
}
|
||||
|
||||
public async Task<bool> IsAdjacentToOwnedAsync(string username, int q, int r)
|
||||
@@ -434,6 +467,101 @@ public class LocationService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HashSet<(int Q, int R)>> GetOccupiedCoordinatesAsync(string username)
|
||||
{
|
||||
var units = await _units.Find(x => x.Username == username).ToListAsync();
|
||||
return units.Select(x => (x.Q, x.R)).ToHashSet();
|
||||
}
|
||||
|
||||
private async Task<Dictionary<(int Q, int R), LocationIntel>> GetIntelByCoordinateAsync(
|
||||
string username,
|
||||
IEnumerable<(int q, int r)> coordinates)
|
||||
{
|
||||
var coordinateList = coordinates.Select(x => (x.q, x.r)).ToList();
|
||||
if (coordinateList.Count == 0)
|
||||
{
|
||||
return new Dictionary<(int Q, int R), LocationIntel>();
|
||||
}
|
||||
|
||||
var minQ = coordinateList.Min(x => x.q);
|
||||
var maxQ = coordinateList.Max(x => x.q);
|
||||
var minR = coordinateList.Min(x => x.r);
|
||||
var maxR = coordinateList.Max(x => x.r);
|
||||
var coordinateSet = coordinateList.ToHashSet();
|
||||
var records = await _intel.Find(x =>
|
||||
x.Username == username &&
|
||||
x.Q >= minQ && x.Q <= maxQ &&
|
||||
x.R >= minR && x.R <= maxR)
|
||||
.ToListAsync();
|
||||
|
||||
return records
|
||||
.Where(x => coordinateSet.Contains((x.Q, x.R)))
|
||||
.ToDictionary(x => (x.Q, x.R), x => x);
|
||||
}
|
||||
|
||||
private async Task RefreshIntelAsync(
|
||||
string username,
|
||||
HashSet<(int Q, int R)> occupiedCoordinates,
|
||||
Dictionary<(int Q, int R), Location> existingByCoordinate)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
foreach (var coordinate in occupiedCoordinates)
|
||||
{
|
||||
if (!existingByCoordinate.TryGetValue(coordinate, out var location))
|
||||
{
|
||||
location = await GetAsync(coordinate.Q, coordinate.R);
|
||||
if (location is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var update = Builders<LocationIntel>.Update
|
||||
.SetOnInsert(x => x.CreatedAtUtc, now)
|
||||
.Set(x => x.Terrain, location.Terrain)
|
||||
.Set(x => x.Kind, location.Kind)
|
||||
.Set(x => x.OwnerUsername, location.OwnerUsername)
|
||||
.Set(x => x.Label, location.Label)
|
||||
.Set(x => x.Level, location.Level)
|
||||
.Set(x => x.BuildingType, location.BuildingType)
|
||||
.Set(x => x.BuildingStatus, location.BuildingStatus)
|
||||
.Set(x => x.BuildingStartedAtUtc, location.BuildingStartedAtUtc)
|
||||
.Set(x => x.BuildingCompletesAtUtc, location.BuildingCompletesAtUtc)
|
||||
.Set(x => x.BuildingBuiltAtUtc, location.BuildingBuiltAtUtc)
|
||||
.Set(x => x.HomeAssignedAtUtc, location.HomeAssignedAtUtc)
|
||||
.Set(x => x.ObservedAtUtc, now)
|
||||
.Set(x => x.UpdatedAtUtc, now);
|
||||
|
||||
await _intel.UpdateOneAsync(
|
||||
x => x.Username == username && x.Q == coordinate.Q && x.R == coordinate.R,
|
||||
update,
|
||||
new UpdateOptions { IsUpsert = true });
|
||||
}
|
||||
}
|
||||
|
||||
private static LocationResponse VisibleResponseFor(
|
||||
string username,
|
||||
Location location,
|
||||
HashSet<(int Q, int R)> occupiedCoordinates,
|
||||
Dictionary<(int Q, int R), LocationIntel> intelByCoordinate)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(location.OwnerUsername) ||
|
||||
string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase) ||
|
||||
occupiedCoordinates.Contains((location.Q, location.R)))
|
||||
{
|
||||
return LocationResponse.FromLocation(location);
|
||||
}
|
||||
|
||||
return intelByCoordinate.TryGetValue((location.Q, location.R), out var intel)
|
||||
? LocationResponse.FromIntel(intel)
|
||||
: LocationResponse.FromLocation(location, revealDetails: false);
|
||||
}
|
||||
|
||||
private async Task<List<Location>> GenerateVisibleLocationsAsync(
|
||||
List<(int q, int r)> missingCoordinates,
|
||||
Dictionary<(int Q, int R), Location> existingByCoordinate)
|
||||
|
||||
Reference in New Issue
Block a user