Adding player sensing
Deploy Promiscuity Auth API / deploy (push) Successful in 47s
Deploy Promiscuity Character API / deploy (push) Successful in 59s
Deploy Promiscuity Inventory API / deploy (push) Successful in 47s
Deploy Promiscuity Locations API / deploy (push) Successful in 47s
k8s smoke test / test (push) Successful in 9s
Deploy Promiscuity Auth API / deploy (push) Successful in 47s
Deploy Promiscuity Character API / deploy (push) Successful in 59s
Deploy Promiscuity Inventory API / deploy (push) Successful in 47s
Deploy Promiscuity Locations API / deploy (push) Successful in 47s
k8s smoke test / test (push) Successful in 9s
This commit is contained in:
@@ -13,6 +13,7 @@ namespace CharacterApi.Controllers;
|
||||
[Route("api/[controller]")]
|
||||
public class CharactersController : ControllerBase
|
||||
{
|
||||
private static readonly TimeSpan PresenceTimeout = TimeSpan.FromSeconds(45);
|
||||
private readonly CharacterStore _characters;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IConfiguration _configuration;
|
||||
@@ -43,6 +44,7 @@ public class CharactersController : ControllerBase
|
||||
Name = req.Name.Trim(),
|
||||
Coord = new Coord { X = 0, Y = 0 },
|
||||
VisionRadius = 3,
|
||||
LastSeenUtc = DateTime.UtcNow,
|
||||
CreatedUtc = DateTime.UtcNow
|
||||
};
|
||||
|
||||
@@ -89,6 +91,8 @@ public class CharactersController : ControllerBase
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
await _characters.TouchAsync(id);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Visible locations requested for character {CharacterId} at ({X},{Y}) radius {VisionRadius} by user {UserId}",
|
||||
character.Id,
|
||||
@@ -164,6 +168,53 @@ public class CharactersController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}/visible-characters")]
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
public async Task<IActionResult> VisibleCharacters(string id)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
return Unauthorized();
|
||||
|
||||
var allowAnyOwner = User.IsInRole("SUPER");
|
||||
var character = await _characters.GetByIdAsync(id);
|
||||
if (character is null)
|
||||
return NotFound();
|
||||
if (!allowAnyOwner && character.OwnerUserId != userId)
|
||||
return Forbid();
|
||||
|
||||
await _characters.TouchAsync(id);
|
||||
|
||||
var onlineSinceUtc = DateTime.UtcNow.Subtract(PresenceTimeout);
|
||||
var visibleCharacters = await _characters.GetVisibleOthersAsync(
|
||||
id,
|
||||
character.Coord.X,
|
||||
character.Coord.Y,
|
||||
character.VisionRadius > 0 ? character.VisionRadius : 3,
|
||||
onlineSinceUtc);
|
||||
|
||||
return Ok(visibleCharacters);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/heartbeat")]
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
public async Task<IActionResult> Heartbeat(string id)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
return Unauthorized();
|
||||
|
||||
var allowAnyOwner = User.IsInRole("SUPER");
|
||||
var character = await _characters.GetByIdAsync(id);
|
||||
if (character is null)
|
||||
return NotFound();
|
||||
if (!allowAnyOwner && character.OwnerUserId != userId)
|
||||
return Forbid();
|
||||
|
||||
await _characters.TouchAsync(id);
|
||||
return Ok(new { lastSeenUtc = DateTime.UtcNow });
|
||||
}
|
||||
|
||||
[HttpPut("{id}/coord")]
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
public async Task<IActionResult> UpdateCoord(string id, [FromBody] UpdateCharacterCoordRequest req)
|
||||
|
||||
@@ -17,5 +17,7 @@ public class Character
|
||||
|
||||
public int VisionRadius { get; set; } = 3;
|
||||
|
||||
public DateTime LastSeenUtc { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace CharacterApi.Models;
|
||||
|
||||
public class VisibleCharacter
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public Coord Coord { get; set; } = new();
|
||||
|
||||
public DateTime LastSeenUtc { get; set; }
|
||||
}
|
||||
@@ -17,6 +17,8 @@ public class CharacterStore
|
||||
|
||||
var ownerIndex = Builders<Character>.IndexKeys.Ascending(c => c.OwnerUserId);
|
||||
_col.Indexes.CreateOne(new CreateIndexModel<Character>(ownerIndex));
|
||||
var coordIndex = Builders<Character>.IndexKeys.Ascending("Coord.X").Ascending("Coord.Y");
|
||||
_col.Indexes.CreateOne(new CreateIndexModel<Character>(coordIndex));
|
||||
}
|
||||
|
||||
public Task CreateAsync(Character character) => _col.InsertOneAsync(character);
|
||||
@@ -30,11 +32,38 @@ public class CharacterStore
|
||||
public async Task<bool> UpdateCoordAsync(string id, Coord coord)
|
||||
{
|
||||
var filter = Builders<Character>.Filter.Eq(c => c.Id, id);
|
||||
var update = Builders<Character>.Update.Set(c => c.Coord, coord);
|
||||
var update = Builders<Character>.Update
|
||||
.Set(c => c.Coord, coord)
|
||||
.Set(c => c.LastSeenUtc, DateTime.UtcNow);
|
||||
var result = await _col.UpdateOneAsync(filter, update);
|
||||
return result.ModifiedCount > 0 || result.MatchedCount > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> TouchAsync(string id)
|
||||
{
|
||||
var filter = Builders<Character>.Filter.Eq(c => c.Id, id);
|
||||
var update = Builders<Character>.Update.Set(c => c.LastSeenUtc, DateTime.UtcNow);
|
||||
var result = await _col.UpdateOneAsync(filter, update);
|
||||
return result.ModifiedCount > 0 || result.MatchedCount > 0;
|
||||
}
|
||||
|
||||
public Task<List<VisibleCharacter>> GetVisibleOthersAsync(string characterId, int x, int y, int radius, DateTime onlineSinceUtc) =>
|
||||
_col.Find(c =>
|
||||
c.Id != characterId &&
|
||||
c.Coord.X >= x - radius &&
|
||||
c.Coord.X <= x + radius &&
|
||||
c.Coord.Y >= y - radius &&
|
||||
c.Coord.Y <= y + radius &&
|
||||
c.LastSeenUtc >= onlineSinceUtc)
|
||||
.Project(c => new VisibleCharacter
|
||||
{
|
||||
Id = c.Id ?? string.Empty,
|
||||
Name = c.Name,
|
||||
Coord = c.Coord,
|
||||
LastSeenUtc = c.LastSeenUtc
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<bool> DeleteForOwnerAsync(string id, string ownerUserId, bool allowAnyOwner)
|
||||
{
|
||||
var filter = Builders<Character>.Filter.Eq(c => c.Id, id);
|
||||
|
||||
Reference in New Issue
Block a user