adding log camps

This commit is contained in:
hz
2026-05-18 20:42:49 -05:00
parent 3167558e04
commit bf75f02dbb
12 changed files with 365 additions and 35 deletions
@@ -10,6 +10,7 @@ public class LocationService
private readonly int _homeMinDistance;
private readonly int _homeSearchRadius;
private readonly int _visibilityDepth;
private readonly int _logCampWoodPerMinute;
public LocationService(IConfiguration cfg, ResourceService resources)
{
@@ -22,6 +23,9 @@ public class LocationService
_homeMinDistance = int.TryParse(cfg["World:HomeMinDistance"], out var minDistance) ? Math.Max(1, minDistance) : 8;
_homeSearchRadius = int.TryParse(cfg["World:HomeSearchRadius"], out var searchRadius) ? Math.Max(_homeMinDistance, searchRadius) : 80;
_visibilityDepth = int.TryParse(cfg["World:VisibilityDepth"], out var visibilityDepth) ? Math.Max(0, visibilityDepth) : 3;
_logCampWoodPerMinute = int.TryParse(cfg["Buildings:LogCampWoodPerMinute"], out var logCampWoodPerMinute)
? Math.Max(1, logCampWoodPerMinute)
: 5;
var coordinateIndex = Builders<Location>.IndexKeys
.Ascending(x => x.Q)
@@ -111,6 +115,53 @@ public class LocationService
return await UpsertAsync(new UpsertLocationRequest { Q = q, R = r, Terrain = "Field" }, username);
}
public async Task<Location?> BuildAsync(BuildLocationRequest request, string username)
{
if (!string.Equals(request.BuildingType, "LogCamp", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Unsupported building type.", nameof(request.BuildingType));
}
var location = await GetAsync(request.Q, request.R);
if (location is null ||
!string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase))
{
return null;
}
if (!string.IsNullOrWhiteSpace(location.BuildingType))
{
throw new InvalidOperationException("This location already has a building.");
}
if (!string.Equals(location.Terrain, "Forest", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Log Camps can only be built on Forest territory.");
}
var now = DateTime.UtcNow;
var update = Builders<Location>.Update
.Set(x => x.BuildingType, "LogCamp")
.Set(x => x.BuildingBuiltAtUtc, now)
.Set(x => x.UpdatedAtUtc, now);
var updated = await _locations.FindOneAndUpdateAsync(
x => x.Id == location.Id && x.OwnerUsername == username && x.BuildingType == null,
update,
new FindOneAndUpdateOptions<Location>
{
ReturnDocument = ReturnDocument.After
});
if (updated is null)
{
throw new InvalidOperationException("This location already has a building.");
}
await _resources.AddWoodGenerationAsync(username, _logCampWoodPerMinute);
return updated;
}
public async Task<Location?> GetHomeAsync(string username) =>
await _locations.Find(x => x.OwnerUsername == username && x.Kind == "Home").FirstOrDefaultAsync();
@@ -32,8 +32,10 @@ public class ResourceService
var update = Builders<PlayerResources>.Update
.SetOnInsert(x => x.Username, username)
.SetOnInsert(x => x.Gold, _startingGold)
.SetOnInsert(x => x.Wood, 0)
.SetOnInsert(x => x.CreatedAtUtc, now)
.SetOnInsert(x => x.LastGoldGeneratedAtUtc, now)
.SetOnInsert(x => x.LastWoodGeneratedAtUtc, now)
.Set(x => x.GoldPerMinute, _headquartersGoldPerMinute)
.Set(x => x.UpdatedAtUtc, now);
@@ -78,29 +80,61 @@ public class ResourceService
return updated is not null;
}
public async Task AddWoodGenerationAsync(string username, int woodPerMinute)
{
if (woodPerMinute <= 0)
{
return;
}
var resources = await EnsureAsync(username);
var now = DateTime.UtcNow;
var update = Builders<PlayerResources>.Update
.Inc(x => x.WoodPerMinute, woodPerMinute)
.Set(x => x.UpdatedAtUtc, now);
if (resources.WoodPerMinute <= 0 || resources.LastWoodGeneratedAtUtc.Year < 2000)
{
update = update.Set(x => x.LastWoodGeneratedAtUtc, now);
}
await _resources.UpdateOneAsync(x => x.Username == username, update);
}
private async Task<PlayerResources> ApplyGoldGenerationAsync(PlayerResources resources)
{
if (resources.GoldPerMinute <= 0)
{
return resources;
}
var now = DateTime.UtcNow;
var elapsed = now - resources.LastGoldGeneratedAtUtc;
var elapsedMinutes = (int)Math.Floor(elapsed.TotalMinutes);
if (elapsedMinutes <= 0)
var goldElapsedMinutes = resources.GoldPerMinute <= 0
? 0
: (int)Math.Floor((now - resources.LastGoldGeneratedAtUtc).TotalMinutes);
var woodElapsedMinutes = resources.WoodPerMinute <= 0 || resources.LastWoodGeneratedAtUtc.Year < 2000
? 0
: (int)Math.Floor((now - resources.LastWoodGeneratedAtUtc).TotalMinutes);
if (goldElapsedMinutes <= 0 && woodElapsedMinutes <= 0)
{
return resources;
}
var generatedGold = elapsedMinutes * resources.GoldPerMinute;
var generationTimestamp = resources.LastGoldGeneratedAtUtc.AddMinutes(elapsedMinutes);
var update = Builders<PlayerResources>.Update
.Inc(x => x.Gold, generatedGold)
.Set(x => x.GoldPerMinute, _headquartersGoldPerMinute)
.Set(x => x.LastGoldGeneratedAtUtc, generationTimestamp)
.Set(x => x.UpdatedAtUtc, now);
if (goldElapsedMinutes > 0)
{
update = update
.Inc(x => x.Gold, goldElapsedMinutes * resources.GoldPerMinute)
.Set(x => x.LastGoldGeneratedAtUtc, resources.LastGoldGeneratedAtUtc.AddMinutes(goldElapsedMinutes));
}
if (woodElapsedMinutes > 0)
{
update = update
.Inc(x => x.Wood, woodElapsedMinutes * resources.WoodPerMinute)
.Set(x => x.LastWoodGeneratedAtUtc, resources.LastWoodGeneratedAtUtc.AddMinutes(woodElapsedMinutes));
}
return await _resources.FindOneAndUpdateAsync(
x => x.Username == resources.Username,
update,