Updating peasants
This commit is contained in:
@@ -148,6 +148,38 @@ public class LocationsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
[HttpPost("assign-worker")]
|
||||
public async Task<ActionResult<LocationResponse>> AssignWorker([FromBody] AssignWorkerRequest request)
|
||||
{
|
||||
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var location = await _locations.AssignWorkerAsync(username, request);
|
||||
return location is null
|
||||
? BadRequest("Select an idle Peasant and a controlled completed building without a worker.")
|
||||
: Ok(LocationResponse.FromLocation(location));
|
||||
}
|
||||
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
[HttpPost("deassign-worker")]
|
||||
public async Task<ActionResult<LocationResponse>> DeassignWorker([FromBody] DeassignWorkerRequest request)
|
||||
{
|
||||
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var location = await _locations.DeassignWorkerAsync(username, request);
|
||||
return location is null
|
||||
? BadRequest("Select a controlled building with an assigned worker.")
|
||||
: Ok(LocationResponse.FromLocation(location));
|
||||
}
|
||||
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
[HttpGet("home")]
|
||||
public async Task<ActionResult<LocationResponse>> GetHome()
|
||||
|
||||
@@ -22,6 +22,19 @@ public class BuildLocationRequest
|
||||
public string BuildingType { get; set; } = "LogCamp";
|
||||
}
|
||||
|
||||
public class AssignWorkerRequest
|
||||
{
|
||||
public int Q { get; set; }
|
||||
public int R { get; set; }
|
||||
public string UnitId { get; set; } = "";
|
||||
}
|
||||
|
||||
public class DeassignWorkerRequest
|
||||
{
|
||||
public int Q { get; set; }
|
||||
public int R { get; set; }
|
||||
}
|
||||
|
||||
public class LocationResponse
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
@@ -37,6 +50,11 @@ public class LocationResponse
|
||||
public DateTime? BuildingStartedAtUtc { get; set; }
|
||||
public DateTime? BuildingCompletesAtUtc { get; set; }
|
||||
public DateTime? BuildingBuiltAtUtc { get; set; }
|
||||
public string? WorkerUnitId { get; set; }
|
||||
public string? WorkerUnitType { get; set; }
|
||||
public int StoredResourceAmount { get; set; }
|
||||
public int StoredResourceCapacity { get; set; }
|
||||
public DateTime? LastResourceGeneratedAtUtc { get; set; }
|
||||
public DateTime? HomeAssignedAtUtc { get; set; }
|
||||
public DateTime UpdatedAtUtc { get; set; }
|
||||
public bool IsVisible { get; set; } = true;
|
||||
@@ -58,6 +76,11 @@ public class LocationResponse
|
||||
BuildingStartedAtUtc = revealDetails ? location.BuildingStartedAtUtc : null,
|
||||
BuildingCompletesAtUtc = revealDetails ? location.BuildingCompletesAtUtc : null,
|
||||
BuildingBuiltAtUtc = revealDetails ? location.BuildingBuiltAtUtc : null,
|
||||
WorkerUnitId = revealDetails ? location.WorkerUnitId : null,
|
||||
WorkerUnitType = revealDetails ? location.WorkerUnitType : null,
|
||||
StoredResourceAmount = revealDetails ? location.StoredResourceAmount : 0,
|
||||
StoredResourceCapacity = revealDetails ? location.StoredResourceCapacity : 0,
|
||||
LastResourceGeneratedAtUtc = revealDetails ? location.LastResourceGeneratedAtUtc : null,
|
||||
HomeAssignedAtUtc = location.HomeAssignedAtUtc,
|
||||
UpdatedAtUtc = location.UpdatedAtUtc,
|
||||
IsVisible = true
|
||||
@@ -80,6 +103,11 @@ public class LocationResponse
|
||||
BuildingStartedAtUtc = intel.BuildingStartedAtUtc,
|
||||
BuildingCompletesAtUtc = intel.BuildingCompletesAtUtc,
|
||||
BuildingBuiltAtUtc = intel.BuildingBuiltAtUtc,
|
||||
WorkerUnitId = intel.WorkerUnitId,
|
||||
WorkerUnitType = intel.WorkerUnitType,
|
||||
StoredResourceAmount = intel.StoredResourceAmount,
|
||||
StoredResourceCapacity = intel.StoredResourceCapacity,
|
||||
LastResourceGeneratedAtUtc = intel.LastResourceGeneratedAtUtc,
|
||||
HomeAssignedAtUtc = intel.HomeAssignedAtUtc,
|
||||
UpdatedAtUtc = intel.ObservedAtUtc,
|
||||
IsVisible = true
|
||||
|
||||
@@ -21,6 +21,11 @@ public class Location
|
||||
public DateTime? BuildingStartedAtUtc { get; set; }
|
||||
public DateTime? BuildingCompletesAtUtc { get; set; }
|
||||
public DateTime? BuildingBuiltAtUtc { get; set; }
|
||||
public string? WorkerUnitId { get; set; }
|
||||
public string? WorkerUnitType { get; set; }
|
||||
public int StoredResourceAmount { get; set; }
|
||||
public int StoredResourceCapacity { get; set; } = 10;
|
||||
public DateTime? LastResourceGeneratedAtUtc { get; set; }
|
||||
public DateTime? HomeAssignedAtUtc { get; set; }
|
||||
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||
|
||||
@@ -22,6 +22,11 @@ public class LocationIntel
|
||||
public DateTime? BuildingStartedAtUtc { get; set; }
|
||||
public DateTime? BuildingCompletesAtUtc { get; set; }
|
||||
public DateTime? BuildingBuiltAtUtc { get; set; }
|
||||
public string? WorkerUnitId { get; set; }
|
||||
public string? WorkerUnitType { get; set; }
|
||||
public int StoredResourceAmount { get; set; }
|
||||
public int StoredResourceCapacity { get; set; } = 10;
|
||||
public DateTime? LastResourceGeneratedAtUtc { get; set; }
|
||||
public DateTime? HomeAssignedAtUtc { get; set; }
|
||||
public DateTime ObservedAtUtc { get; set; } = DateTime.UtcNow;
|
||||
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||
|
||||
@@ -76,6 +76,7 @@ public class PlayerUnitsResponse
|
||||
{
|
||||
public int CompletedScouts { get; set; }
|
||||
public int CompletedArchers { get; set; }
|
||||
public int CompletedPeasants { get; set; }
|
||||
public List<UnitTrainingResponse> Training { get; set; } = [];
|
||||
public List<UnitInstanceResponse> Units { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@ public class GameSettingsService
|
||||
{
|
||||
public const string ScoutTrainingSeconds = "ScoutTrainingSeconds";
|
||||
public const string ArcherTrainingSeconds = "ArcherTrainingSeconds";
|
||||
public const string PeasantTrainingSeconds = "PeasantTrainingSeconds";
|
||||
public const string ScoutGoldCost = "ScoutGoldCost";
|
||||
public const string ArcherGoldCost = "ArcherGoldCost";
|
||||
public const string ArcherWoodCost = "ArcherWoodCost";
|
||||
public const string PeasantGoldCost = "PeasantGoldCost";
|
||||
public const string LogCampBuildSeconds = "LogCampBuildSeconds";
|
||||
public const string MineBuildSeconds = "MineBuildSeconds";
|
||||
public const string BarracksBuildSeconds = "BarracksBuildSeconds";
|
||||
@@ -20,6 +22,7 @@ public class GameSettingsService
|
||||
public const string LogCampWoodPerMinute = "LogCampWoodPerMinute";
|
||||
public const string MineOrePerMinute = "MineOrePerMinute";
|
||||
public const string FarmFoodPerMinute = "FarmFoodPerMinute";
|
||||
public const string ProductionBuildingResourceCapacity = "ProductionBuildingResourceCapacity";
|
||||
public const string StartingGold = "StartingGold";
|
||||
public const string HeadquartersGoldPerMinute = "HeadquartersGoldPerMinute";
|
||||
public const string TownHallGoldCapacity = "TownHallGoldCapacity";
|
||||
|
||||
@@ -263,6 +263,7 @@ public class LocationService
|
||||
var update = Builders<Location>.Update
|
||||
.Set(x => x.BuildingStatus, "Built")
|
||||
.Set(x => x.BuildingBuiltAtUtc, now)
|
||||
.Set(x => x.StoredResourceCapacity, 10)
|
||||
.Set(x => x.UpdatedAtUtc, now);
|
||||
|
||||
var updated = await _locations.FindOneAndUpdateAsync(
|
||||
@@ -275,25 +276,120 @@ public class LocationService
|
||||
|
||||
if (updated is not null)
|
||||
{
|
||||
if (updated.BuildingType == "LogCamp")
|
||||
{
|
||||
var woodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.LogCampWoodPerMinute, _logCampWoodPerMinute);
|
||||
await _resources.AddWoodGenerationAsync(username, woodPerMinute);
|
||||
}
|
||||
else if (updated.BuildingType == "Mine")
|
||||
{
|
||||
var orePerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.MineOrePerMinute, _mineOrePerMinute);
|
||||
await _resources.AddOreGenerationAsync(username, orePerMinute);
|
||||
}
|
||||
else if (updated.BuildingType == "Farm")
|
||||
{
|
||||
var foodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.FarmFoodPerMinute, _farmFoodPerMinute);
|
||||
await _resources.AddFoodGenerationAsync(username, foodPerMinute);
|
||||
}
|
||||
await RecalculateWorkerGenerationAsync(username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Location?> AssignWorkerAsync(string username, AssignWorkerRequest request)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var location = await GetAsync(request.Q, request.R);
|
||||
if (location is null ||
|
||||
!string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.IsNullOrWhiteSpace(location.BuildingType) ||
|
||||
location.BuildingStatus == "Building" ||
|
||||
!string.IsNullOrWhiteSpace(location.WorkerUnitId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var unit = await _units.Find(x =>
|
||||
x.Id == request.UnitId &&
|
||||
x.Username == username &&
|
||||
x.UnitType == "Peasant" &&
|
||||
x.Status == "Idle")
|
||||
.FirstOrDefaultAsync();
|
||||
if (unit is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var claimed = await _units.UpdateOneAsync(
|
||||
x => x.Id == unit.Id && x.Status == "Idle",
|
||||
Builders<UnitInstance>.Update
|
||||
.Set(x => x.Q, request.Q)
|
||||
.Set(x => x.R, request.R)
|
||||
.Set(x => x.Status, "Working")
|
||||
.Set(x => x.UpdatedAtUtc, now));
|
||||
if (claimed.ModifiedCount == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var updated = await _locations.FindOneAndUpdateAsync(
|
||||
x => x.Id == location.Id && (x.WorkerUnitId == null || x.WorkerUnitId == ""),
|
||||
Builders<Location>.Update
|
||||
.Set(x => x.WorkerUnitId, unit.Id)
|
||||
.Set(x => x.WorkerUnitType, "Peasant")
|
||||
.Set(x => x.LastResourceGeneratedAtUtc, now)
|
||||
.Set(x => x.UpdatedAtUtc, now),
|
||||
new FindOneAndUpdateOptions<Location> { ReturnDocument = ReturnDocument.After });
|
||||
|
||||
if (updated is null)
|
||||
{
|
||||
await _units.UpdateOneAsync(
|
||||
x => x.Id == unit.Id,
|
||||
Builders<UnitInstance>.Update
|
||||
.Set(x => x.Status, "Idle")
|
||||
.Set(x => x.UpdatedAtUtc, DateTime.UtcNow));
|
||||
return null;
|
||||
}
|
||||
|
||||
await RecalculateWorkerGenerationAsync(username);
|
||||
return updated;
|
||||
}
|
||||
|
||||
public async Task<Location?> DeassignWorkerAsync(string username, DeassignWorkerRequest request)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var location = await GetAsync(request.Q, request.R);
|
||||
if (location is null ||
|
||||
!string.Equals(location.OwnerUsername, username, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.IsNullOrWhiteSpace(location.WorkerUnitId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await _units.UpdateOneAsync(
|
||||
x => x.Id == location.WorkerUnitId && x.Username == username && x.Status == "Working",
|
||||
Builders<UnitInstance>.Update
|
||||
.Set(x => x.Q, request.Q)
|
||||
.Set(x => x.R, request.R)
|
||||
.Set(x => x.Status, "Idle")
|
||||
.Set(x => x.UpdatedAtUtc, now));
|
||||
|
||||
var updated = await _locations.FindOneAndUpdateAsync(
|
||||
x => x.Id == location.Id,
|
||||
Builders<Location>.Update
|
||||
.Unset(x => x.WorkerUnitId)
|
||||
.Unset(x => x.WorkerUnitType)
|
||||
.Set(x => x.UpdatedAtUtc, now),
|
||||
new FindOneAndUpdateOptions<Location> { ReturnDocument = ReturnDocument.After });
|
||||
|
||||
await RecalculateWorkerGenerationAsync(username);
|
||||
return updated;
|
||||
}
|
||||
|
||||
private async Task RecalculateWorkerGenerationAsync(string username)
|
||||
{
|
||||
var built = await _locations.Find(x =>
|
||||
x.OwnerUsername == username &&
|
||||
!string.IsNullOrWhiteSpace(x.WorkerUnitId) &&
|
||||
(x.BuildingStatus == "Built" || x.BuildingStatus == null || x.BuildingStatus == "") &&
|
||||
(x.BuildingType == "LogCamp" || x.BuildingType == "Mine" || x.BuildingType == "Farm"))
|
||||
.ToListAsync();
|
||||
|
||||
var woodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.LogCampWoodPerMinute, _logCampWoodPerMinute);
|
||||
var orePerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.MineOrePerMinute, _mineOrePerMinute);
|
||||
var foodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.FarmFoodPerMinute, _farmFoodPerMinute);
|
||||
await _resources.SetBuildingGenerationAsync(
|
||||
username,
|
||||
built.Count(x => x.BuildingType == "LogCamp") * woodPerMinute,
|
||||
built.Count(x => x.BuildingType == "Mine") * orePerMinute,
|
||||
built.Count(x => x.BuildingType == "Farm") * foodPerMinute);
|
||||
}
|
||||
|
||||
public async Task<Location?> GetHomeAsync(string username) =>
|
||||
await _locations.Find(x => x.OwnerUsername == username && x.Kind == "Home").FirstOrDefaultAsync();
|
||||
|
||||
@@ -337,6 +433,7 @@ public class LocationService
|
||||
public async Task<List<LocationResponse>> GetVisibleAsync(string username, int? visibilityDepth = null)
|
||||
{
|
||||
await CompleteFinishedBuildingsAsync(username);
|
||||
await RecalculateWorkerGenerationAsync(username);
|
||||
|
||||
var ownedLocations = await _locations.Find(x => x.OwnerUsername == username).ToListAsync();
|
||||
if (ownedLocations.Count == 0)
|
||||
@@ -551,6 +648,11 @@ public class LocationService
|
||||
.Set(x => x.BuildingStartedAtUtc, location.BuildingStartedAtUtc)
|
||||
.Set(x => x.BuildingCompletesAtUtc, location.BuildingCompletesAtUtc)
|
||||
.Set(x => x.BuildingBuiltAtUtc, location.BuildingBuiltAtUtc)
|
||||
.Set(x => x.WorkerUnitId, location.WorkerUnitId)
|
||||
.Set(x => x.WorkerUnitType, location.WorkerUnitType)
|
||||
.Set(x => x.StoredResourceAmount, location.StoredResourceAmount)
|
||||
.Set(x => x.StoredResourceCapacity, location.StoredResourceCapacity)
|
||||
.Set(x => x.LastResourceGeneratedAtUtc, location.LastResourceGeneratedAtUtc)
|
||||
.Set(x => x.HomeAssignedAtUtc, location.HomeAssignedAtUtc)
|
||||
.Set(x => x.ObservedAtUtc, now)
|
||||
.Set(x => x.UpdatedAtUtc, now);
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace LocationsApi.Services;
|
||||
public class ResourceService
|
||||
{
|
||||
private readonly IMongoCollection<PlayerResources> _resources;
|
||||
private readonly IMongoCollection<Location> _locations;
|
||||
private readonly GameSettingsService _settings;
|
||||
private readonly int _startingGold;
|
||||
private readonly int _headquartersGoldPerMinute;
|
||||
@@ -13,6 +14,7 @@ public class ResourceService
|
||||
private readonly int _townHallWoodCapacity;
|
||||
private readonly int _townHallOreCapacity;
|
||||
private readonly int _townHallFoodCapacity;
|
||||
private readonly int _productionBuildingResourceCapacity;
|
||||
|
||||
public ResourceService(IConfiguration cfg, GameSettingsService settings)
|
||||
{
|
||||
@@ -21,6 +23,7 @@ public class ResourceService
|
||||
var client = new MongoClient(connectionString);
|
||||
var database = client.GetDatabase(databaseName);
|
||||
_resources = database.GetCollection<PlayerResources>("PlayerResources");
|
||||
_locations = database.GetCollection<Location>("Locations");
|
||||
_settings = settings;
|
||||
_startingGold = int.TryParse(cfg["Resources:StartingGold"], out var startingGold) ? Math.Max(0, startingGold) : 100;
|
||||
_headquartersGoldPerMinute = int.TryParse(cfg["Resources:HeadquartersGoldPerMinute"], out var goldPerMinute)
|
||||
@@ -38,6 +41,9 @@ public class ResourceService
|
||||
_townHallFoodCapacity = int.TryParse(cfg["Resources:TownHallFoodCapacity"], out var foodCapacity)
|
||||
? Math.Max(0, foodCapacity)
|
||||
: 100;
|
||||
_productionBuildingResourceCapacity = int.TryParse(cfg["Buildings:ProductionBuildingResourceCapacity"], out var productionBuildingResourceCapacity)
|
||||
? Math.Max(0, productionBuildingResourceCapacity)
|
||||
: 10;
|
||||
|
||||
_resources.Indexes.CreateOne(new CreateIndexModel<PlayerResources>(
|
||||
Builders<PlayerResources>.IndexKeys.Ascending(x => x.Username),
|
||||
@@ -85,7 +91,8 @@ public class ResourceService
|
||||
|
||||
public async Task<PlayerResources> GetAsync(string username)
|
||||
{
|
||||
return await EnsureAsync(username);
|
||||
var resources = await EnsureAsync(username);
|
||||
return await ApplyProductionBuildingDeliveryAsync(resources);
|
||||
}
|
||||
|
||||
public async Task<bool> TrySpendGoldAsync(string username, int amount)
|
||||
@@ -207,6 +214,33 @@ public class ResourceService
|
||||
await _resources.UpdateOneAsync(x => x.Username == username, update);
|
||||
}
|
||||
|
||||
public async Task SetBuildingGenerationAsync(string username, int woodPerMinute, int orePerMinute, int foodPerMinute)
|
||||
{
|
||||
await EnsureAsync(username);
|
||||
var now = DateTime.UtcNow;
|
||||
var update = Builders<PlayerResources>.Update
|
||||
.Set(x => x.WoodPerMinute, Math.Max(0, woodPerMinute))
|
||||
.Set(x => x.OrePerMinute, Math.Max(0, orePerMinute))
|
||||
.Set(x => x.FoodPerMinute, Math.Max(0, foodPerMinute))
|
||||
.Set(x => x.UpdatedAtUtc, now);
|
||||
|
||||
if (woodPerMinute > 0)
|
||||
{
|
||||
update = update.Set(x => x.LastWoodGeneratedAtUtc, now);
|
||||
}
|
||||
if (orePerMinute > 0)
|
||||
{
|
||||
update = update.Set(x => x.LastOreGeneratedAtUtc, now);
|
||||
}
|
||||
if (foodPerMinute > 0)
|
||||
{
|
||||
update = update.Set(x => x.LastFoodGeneratedAtUtc, now);
|
||||
}
|
||||
|
||||
await _resources.UpdateOneAsync(x => x.Username == username, update);
|
||||
}
|
||||
|
||||
|
||||
private async Task<PlayerResources> ApplyResourceGenerationAsync(PlayerResources resources)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
@@ -218,22 +252,13 @@ public class ResourceService
|
||||
var goldElapsedMinutes = 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);
|
||||
var oreElapsedMinutes = resources.OrePerMinute <= 0 || resources.LastOreGeneratedAtUtc.Year < 2000
|
||||
? 0
|
||||
: (int)Math.Floor((now - resources.LastOreGeneratedAtUtc).TotalMinutes);
|
||||
var foodElapsedMinutes = resources.FoodPerMinute <= 0 || resources.LastFoodGeneratedAtUtc.Year < 2000
|
||||
? 0
|
||||
: (int)Math.Floor((now - resources.LastFoodGeneratedAtUtc).TotalMinutes);
|
||||
var cappedGold = Math.Min(resources.Gold, goldCapacity);
|
||||
var cappedWood = Math.Min(resources.Wood, woodCapacity);
|
||||
var cappedOre = Math.Min(resources.Ore, oreCapacity);
|
||||
var cappedFood = Math.Min(resources.Food, foodCapacity);
|
||||
var isOverCapacity = resources.Gold != cappedGold || resources.Wood != cappedWood || resources.Ore != cappedOre || resources.Food != cappedFood;
|
||||
|
||||
if (goldElapsedMinutes <= 0 && woodElapsedMinutes <= 0 && oreElapsedMinutes <= 0 && foodElapsedMinutes <= 0 && !isOverCapacity)
|
||||
if (goldElapsedMinutes <= 0 && !isOverCapacity)
|
||||
{
|
||||
return resources;
|
||||
}
|
||||
@@ -263,30 +288,6 @@ public class ResourceService
|
||||
.Set(x => x.LastGoldGeneratedAtUtc, resources.LastGoldGeneratedAtUtc.AddMinutes(goldElapsedMinutes));
|
||||
}
|
||||
|
||||
if (woodElapsedMinutes > 0)
|
||||
{
|
||||
var generatedWood = Math.Min(woodCapacity, cappedWood + (woodElapsedMinutes * resources.WoodPerMinute));
|
||||
update = update
|
||||
.Set(x => x.Wood, generatedWood)
|
||||
.Set(x => x.LastWoodGeneratedAtUtc, resources.LastWoodGeneratedAtUtc.AddMinutes(woodElapsedMinutes));
|
||||
}
|
||||
|
||||
if (oreElapsedMinutes > 0)
|
||||
{
|
||||
var generatedOre = Math.Min(oreCapacity, cappedOre + (oreElapsedMinutes * resources.OrePerMinute));
|
||||
update = update
|
||||
.Set(x => x.Ore, generatedOre)
|
||||
.Set(x => x.LastOreGeneratedAtUtc, resources.LastOreGeneratedAtUtc.AddMinutes(oreElapsedMinutes));
|
||||
}
|
||||
|
||||
if (foodElapsedMinutes > 0)
|
||||
{
|
||||
var generatedFood = Math.Min(foodCapacity, cappedFood + (foodElapsedMinutes * resources.FoodPerMinute));
|
||||
update = update
|
||||
.Set(x => x.Food, generatedFood)
|
||||
.Set(x => x.LastFoodGeneratedAtUtc, resources.LastFoodGeneratedAtUtc.AddMinutes(foodElapsedMinutes));
|
||||
}
|
||||
|
||||
return await _resources.FindOneAndUpdateAsync(
|
||||
x => x.Username == resources.Username,
|
||||
update,
|
||||
@@ -295,4 +296,85 @@ public class ResourceService
|
||||
ReturnDocument = ReturnDocument.After
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<PlayerResources> ApplyProductionBuildingDeliveryAsync(PlayerResources resources)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var woodCapacity = await _settings.GetNonNegativeIntAsync(GameSettingsService.TownHallWoodCapacity, _townHallWoodCapacity);
|
||||
var oreCapacity = await _settings.GetNonNegativeIntAsync(GameSettingsService.TownHallOreCapacity, _townHallOreCapacity);
|
||||
var foodCapacity = await _settings.GetNonNegativeIntAsync(GameSettingsService.TownHallFoodCapacity, _townHallFoodCapacity);
|
||||
var buildingCapacity = await _settings.GetNonNegativeIntAsync(GameSettingsService.ProductionBuildingResourceCapacity, _productionBuildingResourceCapacity);
|
||||
var logCampWoodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.LogCampWoodPerMinute, 5);
|
||||
var mineOrePerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.MineOrePerMinute, 5);
|
||||
var farmFoodPerMinute = await _settings.GetNonNegativeIntAsync(GameSettingsService.FarmFoodPerMinute, 5);
|
||||
|
||||
var buildings = await _locations.Find(x =>
|
||||
x.OwnerUsername == resources.Username &&
|
||||
!string.IsNullOrWhiteSpace(x.WorkerUnitId) &&
|
||||
(x.BuildingStatus == "Built" || x.BuildingStatus == null || x.BuildingStatus == "") &&
|
||||
(x.BuildingType == "LogCamp" || x.BuildingType == "Mine" || x.BuildingType == "Farm"))
|
||||
.ToListAsync();
|
||||
|
||||
var wood = Math.Min(resources.Wood, woodCapacity);
|
||||
var ore = Math.Min(resources.Ore, oreCapacity);
|
||||
var food = Math.Min(resources.Food, foodCapacity);
|
||||
|
||||
foreach (var building in buildings)
|
||||
{
|
||||
var rate = building.BuildingType switch
|
||||
{
|
||||
"LogCamp" => logCampWoodPerMinute,
|
||||
"Mine" => mineOrePerMinute,
|
||||
"Farm" => farmFoodPerMinute,
|
||||
_ => 0
|
||||
};
|
||||
var lastGenerated = building.LastResourceGeneratedAtUtc ?? building.BuildingBuiltAtUtc ?? now;
|
||||
var elapsedMinutes = rate <= 0 ? 0 : (int)Math.Floor((now - lastGenerated).TotalMinutes);
|
||||
var stored = Math.Min(buildingCapacity, Math.Max(0, building.StoredResourceAmount) + (elapsedMinutes * rate));
|
||||
|
||||
var delivered = 0;
|
||||
if (building.BuildingType == "LogCamp")
|
||||
{
|
||||
delivered = Math.Min(stored, Math.Max(0, woodCapacity - wood));
|
||||
wood += delivered;
|
||||
}
|
||||
else if (building.BuildingType == "Mine")
|
||||
{
|
||||
delivered = Math.Min(stored, Math.Max(0, oreCapacity - ore));
|
||||
ore += delivered;
|
||||
}
|
||||
else if (building.BuildingType == "Farm")
|
||||
{
|
||||
delivered = Math.Min(stored, Math.Max(0, foodCapacity - food));
|
||||
food += delivered;
|
||||
}
|
||||
|
||||
var update = Builders<Location>.Update
|
||||
.Set(x => x.StoredResourceAmount, stored - delivered)
|
||||
.Set(x => x.StoredResourceCapacity, buildingCapacity)
|
||||
.Set(x => x.UpdatedAtUtc, now);
|
||||
|
||||
if (elapsedMinutes > 0 || building.LastResourceGeneratedAtUtc is null)
|
||||
{
|
||||
update = update.Set(x => x.LastResourceGeneratedAtUtc, lastGenerated.AddMinutes(elapsedMinutes));
|
||||
}
|
||||
|
||||
await _locations.UpdateOneAsync(x => x.Id == building.Id, update);
|
||||
}
|
||||
|
||||
return await _resources.FindOneAndUpdateAsync(
|
||||
x => x.Username == resources.Username,
|
||||
Builders<PlayerResources>.Update
|
||||
.Set(x => x.Wood, wood)
|
||||
.Set(x => x.Ore, ore)
|
||||
.Set(x => x.Food, food)
|
||||
.Set(x => x.WoodCapacity, woodCapacity)
|
||||
.Set(x => x.OreCapacity, oreCapacity)
|
||||
.Set(x => x.FoodCapacity, foodCapacity)
|
||||
.Set(x => x.UpdatedAtUtc, now),
|
||||
new FindOneAndUpdateOptions<PlayerResources>
|
||||
{
|
||||
ReturnDocument = ReturnDocument.After
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ public class UnitTrainingService
|
||||
{
|
||||
private const string ScoutUnitType = "Scout";
|
||||
private const string ArcherUnitType = "Archer";
|
||||
private const string PeasantUnitType = "Peasant";
|
||||
|
||||
private readonly IMongoCollection<UnitTraining> _training;
|
||||
private readonly IMongoCollection<UnitInstance> _units;
|
||||
@@ -16,8 +17,10 @@ public class UnitTrainingService
|
||||
private readonly int _scoutGoldCost;
|
||||
private readonly int _archerGoldCost;
|
||||
private readonly int _archerWoodCost;
|
||||
private readonly int _peasantGoldCost;
|
||||
private readonly int _defaultScoutTrainingSeconds;
|
||||
private readonly int _defaultArcherTrainingSeconds;
|
||||
private readonly int _defaultPeasantTrainingSeconds;
|
||||
private readonly int _visibilityDepth;
|
||||
|
||||
public UnitTrainingService(IConfiguration cfg, ResourceService resources, GameSettingsService settings)
|
||||
@@ -34,12 +37,16 @@ public class UnitTrainingService
|
||||
_scoutGoldCost = int.TryParse(cfg["Units:ScoutGoldCost"], out var scoutGoldCost) ? Math.Max(0, scoutGoldCost) : 50;
|
||||
_archerGoldCost = int.TryParse(cfg["Units:ArcherGoldCost"], out var archerGoldCost) ? Math.Max(0, archerGoldCost) : 100;
|
||||
_archerWoodCost = int.TryParse(cfg["Units:ArcherWoodCost"], out var archerWoodCost) ? Math.Max(0, archerWoodCost) : 100;
|
||||
_peasantGoldCost = int.TryParse(cfg["Units:PeasantGoldCost"], out var peasantGoldCost) ? Math.Max(0, peasantGoldCost) : 25;
|
||||
_defaultScoutTrainingSeconds = int.TryParse(cfg["Units:ScoutTrainingSeconds"], out var scoutTrainingSeconds)
|
||||
? Math.Max(1, scoutTrainingSeconds)
|
||||
: 60;
|
||||
_defaultArcherTrainingSeconds = int.TryParse(cfg["Units:ArcherTrainingSeconds"], out var archerTrainingSeconds)
|
||||
? Math.Max(1, archerTrainingSeconds)
|
||||
: 60;
|
||||
_defaultPeasantTrainingSeconds = int.TryParse(cfg["Units:PeasantTrainingSeconds"], out var peasantTrainingSeconds)
|
||||
? Math.Max(1, peasantTrainingSeconds)
|
||||
: 60;
|
||||
_visibilityDepth = int.TryParse(cfg["World:VisibilityDepth"], out var visibilityDepth) ? Math.Max(0, visibilityDepth) : 3;
|
||||
|
||||
_training.Indexes.CreateOne(new CreateIndexModel<UnitTraining>(
|
||||
@@ -64,6 +71,7 @@ public class UnitTrainingService
|
||||
var allUnits = await _units.Find(x => x.Username == username).ToListAsync();
|
||||
var completedScouts = allTraining.Count(x => x.UnitType == ScoutUnitType && x.Status == "Completed");
|
||||
var completedArchers = allUnits.Count(x => x.UnitType == ArcherUnitType);
|
||||
var completedPeasants = allUnits.Count(x => x.UnitType == PeasantUnitType && x.Status == "Idle");
|
||||
var activeTraining = allTraining
|
||||
.Where(x => x.Status == "Training")
|
||||
.OrderBy(x => x.CompletesAtUtc)
|
||||
@@ -74,6 +82,7 @@ public class UnitTrainingService
|
||||
{
|
||||
CompletedScouts = completedScouts,
|
||||
CompletedArchers = completedArchers,
|
||||
CompletedPeasants = completedPeasants,
|
||||
Training = activeTraining,
|
||||
Units = allUnits
|
||||
.OrderBy(x => x.Q)
|
||||
@@ -106,10 +115,20 @@ public class UnitTrainingService
|
||||
throw new InvalidOperationException("A completed Barracks is required to train Archers.");
|
||||
}
|
||||
}
|
||||
else if (normalizedUnitType == PeasantUnitType)
|
||||
{
|
||||
origin = await _locations.Find(x => x.OwnerUsername == username && x.Kind == "Home").FirstOrDefaultAsync();
|
||||
if (origin is null)
|
||||
{
|
||||
throw new InvalidOperationException("A town hall is required to train Peasants.");
|
||||
}
|
||||
}
|
||||
|
||||
var goldCost = normalizedUnitType == ArcherUnitType
|
||||
? await _settings.GetNonNegativeIntAsync(GameSettingsService.ArcherGoldCost, _archerGoldCost)
|
||||
: await _settings.GetNonNegativeIntAsync(GameSettingsService.ScoutGoldCost, _scoutGoldCost);
|
||||
: normalizedUnitType == PeasantUnitType
|
||||
? await _settings.GetNonNegativeIntAsync(GameSettingsService.PeasantGoldCost, _peasantGoldCost)
|
||||
: await _settings.GetNonNegativeIntAsync(GameSettingsService.ScoutGoldCost, _scoutGoldCost);
|
||||
var woodCost = normalizedUnitType == ArcherUnitType
|
||||
? await _settings.GetNonNegativeIntAsync(GameSettingsService.ArcherWoodCost, _archerWoodCost)
|
||||
: 0;
|
||||
@@ -124,6 +143,8 @@ public class UnitTrainingService
|
||||
var now = DateTime.UtcNow;
|
||||
var trainingSeconds = normalizedUnitType == ArcherUnitType
|
||||
? await _settings.GetIntAsync(GameSettingsService.ArcherTrainingSeconds, _defaultArcherTrainingSeconds)
|
||||
: normalizedUnitType == PeasantUnitType
|
||||
? await _settings.GetIntAsync(GameSettingsService.PeasantTrainingSeconds, _defaultPeasantTrainingSeconds)
|
||||
: await _settings.GetIntAsync(GameSettingsService.ScoutTrainingSeconds, _defaultScoutTrainingSeconds);
|
||||
var training = new UnitTraining
|
||||
{
|
||||
@@ -350,7 +371,7 @@ public class UnitTrainingService
|
||||
ReturnDocument = ReturnDocument.After
|
||||
});
|
||||
|
||||
if (completed is null || completed.UnitType != ArcherUnitType)
|
||||
if (completed is null || (completed.UnitType != ArcherUnitType && completed.UnitType != PeasantUnitType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -358,7 +379,7 @@ public class UnitTrainingService
|
||||
var unit = new UnitInstance
|
||||
{
|
||||
Username = completed.Username,
|
||||
UnitType = ArcherUnitType,
|
||||
UnitType = completed.UnitType,
|
||||
Q = completed.OriginQ ?? 0,
|
||||
R = completed.OriginR ?? 0,
|
||||
Status = "Idle",
|
||||
@@ -384,6 +405,7 @@ public class UnitTrainingService
|
||||
{
|
||||
"scout" => ScoutUnitType,
|
||||
"archer" => ArcherUnitType,
|
||||
"peasant" => PeasantUnitType,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
"Units": {
|
||||
"ScoutGoldCost": 50,
|
||||
"ScoutTrainingSeconds": 60,
|
||||
"PeasantGoldCost": 25,
|
||||
"PeasantTrainingSeconds": 60,
|
||||
"ArcherGoldCost": 100,
|
||||
"ArcherWoodCost": 100,
|
||||
"ArcherTrainingSeconds": 60
|
||||
@@ -40,6 +42,7 @@
|
||||
"MineOrePerMinute": 5,
|
||||
"MineBuildSeconds": 60,
|
||||
"FarmFoodPerMinute": 5,
|
||||
"ProductionBuildingResourceCapacity": 10,
|
||||
"BarracksBuildSeconds": 60,
|
||||
"FarmBuildSeconds": 60,
|
||||
"BarracksGoldCost": 500,
|
||||
|
||||
@@ -19,6 +19,8 @@ data:
|
||||
Resources__TownHallFoodCapacity: "100"
|
||||
Units__ScoutGoldCost: "50"
|
||||
Units__ScoutTrainingSeconds: "60"
|
||||
Units__PeasantGoldCost: "25"
|
||||
Units__PeasantTrainingSeconds: "60"
|
||||
Units__ArcherGoldCost: "100"
|
||||
Units__ArcherWoodCost: "100"
|
||||
Units__ArcherTrainingSeconds: "60"
|
||||
@@ -27,6 +29,7 @@ data:
|
||||
Buildings__MineOrePerMinute: "5"
|
||||
Buildings__MineBuildSeconds: "60"
|
||||
Buildings__FarmFoodPerMinute: "5"
|
||||
Buildings__ProductionBuildingResourceCapacity: "10"
|
||||
Buildings__BarracksBuildSeconds: "60"
|
||||
Buildings__FarmBuildSeconds: "60"
|
||||
Buildings__BarracksGoldCost: "500"
|
||||
|
||||
Reference in New Issue
Block a user