Files
territory-autobattler/microservices/LocationsApi/Services/ResourceService.cs
T
2026-05-18 21:37:48 -05:00

181 lines
6.4 KiB
C#

using LocationsApi.Models;
using MongoDB.Driver;
namespace LocationsApi.Services;
public class ResourceService
{
private readonly IMongoCollection<PlayerResources> _resources;
private readonly int _startingGold;
private readonly int _headquartersGoldPerMinute;
public ResourceService(IConfiguration cfg)
{
var connectionString = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017";
var databaseName = cfg["MongoDB:DatabaseName"] ?? "territory-autobattler";
var client = new MongoClient(connectionString);
var database = client.GetDatabase(databaseName);
_resources = database.GetCollection<PlayerResources>("PlayerResources");
_startingGold = int.TryParse(cfg["Resources:StartingGold"], out var startingGold) ? Math.Max(0, startingGold) : 100;
_headquartersGoldPerMinute = int.TryParse(cfg["Resources:HeadquartersGoldPerMinute"], out var goldPerMinute)
? Math.Max(0, goldPerMinute)
: 10;
_resources.Indexes.CreateOne(new CreateIndexModel<PlayerResources>(
Builders<PlayerResources>.IndexKeys.Ascending(x => x.Username),
new CreateIndexOptions { Unique = true }));
}
public async Task<PlayerResources> EnsureAsync(string username)
{
var now = DateTime.UtcNow;
var update = Builders<PlayerResources>.Update
.SetOnInsert(x => x.Username, username)
.SetOnInsert(x => x.Gold, _startingGold)
.SetOnInsert(x => x.Wood, 0)
.SetOnInsert(x => x.Ore, 0)
.SetOnInsert(x => x.CreatedAtUtc, now)
.SetOnInsert(x => x.LastGoldGeneratedAtUtc, now)
.SetOnInsert(x => x.LastWoodGeneratedAtUtc, now)
.SetOnInsert(x => x.LastOreGeneratedAtUtc, now)
.Set(x => x.GoldPerMinute, _headquartersGoldPerMinute)
.Set(x => x.UpdatedAtUtc, now);
var resources = await _resources.FindOneAndUpdateAsync(
x => x.Username == username,
update,
new FindOneAndUpdateOptions<PlayerResources>
{
IsUpsert = true,
ReturnDocument = ReturnDocument.After
});
return await ApplyGoldGenerationAsync(resources);
}
public async Task<PlayerResources> GetAsync(string username)
{
return await EnsureAsync(username);
}
public async Task<bool> TrySpendGoldAsync(string username, int amount)
{
if (amount <= 0)
{
return true;
}
await EnsureAsync(username);
var update = Builders<PlayerResources>.Update
.Inc(x => x.Gold, -amount)
.Set(x => x.UpdatedAtUtc, DateTime.UtcNow);
var updated = await _resources.FindOneAndUpdateAsync(
x => x.Username == username && x.Gold >= amount,
update,
new FindOneAndUpdateOptions<PlayerResources>
{
ReturnDocument = ReturnDocument.After
});
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);
}
public async Task AddOreGenerationAsync(string username, int orePerMinute)
{
if (orePerMinute <= 0)
{
return;
}
var resources = await EnsureAsync(username);
var now = DateTime.UtcNow;
var update = Builders<PlayerResources>.Update
.Inc(x => x.OrePerMinute, orePerMinute)
.Set(x => x.UpdatedAtUtc, now);
if (resources.OrePerMinute <= 0 || resources.LastOreGeneratedAtUtc.Year < 2000)
{
update = update.Set(x => x.LastOreGeneratedAtUtc, now);
}
await _resources.UpdateOneAsync(x => x.Username == username, update);
}
private async Task<PlayerResources> ApplyGoldGenerationAsync(PlayerResources resources)
{
var now = DateTime.UtcNow;
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);
var oreElapsedMinutes = resources.OrePerMinute <= 0 || resources.LastOreGeneratedAtUtc.Year < 2000
? 0
: (int)Math.Floor((now - resources.LastOreGeneratedAtUtc).TotalMinutes);
if (goldElapsedMinutes <= 0 && woodElapsedMinutes <= 0 && oreElapsedMinutes <= 0)
{
return resources;
}
var update = Builders<PlayerResources>.Update
.Set(x => x.GoldPerMinute, _headquartersGoldPerMinute)
.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));
}
if (oreElapsedMinutes > 0)
{
update = update
.Inc(x => x.Ore, oreElapsedMinutes * resources.OrePerMinute)
.Set(x => x.LastOreGeneratedAtUtc, resources.LastOreGeneratedAtUtc.AddMinutes(oreElapsedMinutes));
}
return await _resources.FindOneAndUpdateAsync(
x => x.Username == resources.Username,
update,
new FindOneAndUpdateOptions<PlayerResources>
{
ReturnDocument = ReturnDocument.After
});
}
}