135 lines
4.7 KiB
C#
135 lines
4.7 KiB
C#
using NeonSprawl.Server.Game.PositionState;
|
|
|
|
namespace NeonSprawl.Server.Game.Items;
|
|
|
|
/// <summary>
|
|
/// Maps <c>GET</c>/<c>POST /game/players/{{id}}/inventory</c> — read snapshot and add/remove mutations (NEO-55).
|
|
/// </summary>
|
|
public static class PlayerInventoryApi
|
|
{
|
|
public static WebApplication MapPlayerInventoryApi(this WebApplication app)
|
|
{
|
|
app.MapGet(
|
|
"/game/players/{id}/inventory",
|
|
(string id, IPositionStateStore positions, IPlayerInventoryStore store) =>
|
|
{
|
|
var trimmedId = id.Trim();
|
|
if (trimmedId.Length == 0 || !positions.TryGetPosition(trimmedId, out _))
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
if (!store.TryGetSnapshot(trimmedId, out var snapshot))
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
return Results.Json(MapSnapshot(trimmedId, snapshot));
|
|
});
|
|
|
|
app.MapPost(
|
|
"/game/players/{id}/inventory",
|
|
(string id, PlayerInventoryMutationRequest? body, IPositionStateStore positions,
|
|
IItemDefinitionRegistry registry, IPlayerInventoryStore store) =>
|
|
{
|
|
if (body is null || body.SchemaVersion != PlayerInventoryMutationRequest.CurrentSchemaVersion)
|
|
{
|
|
return Results.BadRequest();
|
|
}
|
|
|
|
if (!TryParseMutationKind(body.MutationKind, out var isAdd))
|
|
{
|
|
return Results.BadRequest();
|
|
}
|
|
|
|
var trimmedId = id.Trim();
|
|
if (trimmedId.Length == 0 || !positions.TryGetPosition(trimmedId, out _))
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
var outcome = isAdd
|
|
? PlayerInventoryOperations.TryAddStack(trimmedId, body.ItemId, body.Quantity, registry, store)
|
|
: PlayerInventoryOperations.TryRemoveStack(trimmedId, body.ItemId, body.Quantity, registry, store);
|
|
|
|
return outcome.Kind switch
|
|
{
|
|
PlayerInventoryMutationKind.StoreMissing => Results.NotFound(),
|
|
PlayerInventoryMutationKind.Denied => Results.Json(
|
|
BuildMutationResponse(trimmedId, applied: false, outcome.ReasonCode, outcome.Snapshot, store)),
|
|
PlayerInventoryMutationKind.Applied => Results.Json(
|
|
BuildMutationResponse(trimmedId, applied: true, reasonCode: null, outcome.Snapshot, store)),
|
|
_ => throw new InvalidOperationException($"Unexpected inventory mutation outcome: {outcome.Kind}"),
|
|
};
|
|
});
|
|
|
|
return app;
|
|
}
|
|
|
|
internal static PlayerInventorySnapshotResponse MapSnapshot(string playerId, PlayerInventorySnapshot snapshot) =>
|
|
new()
|
|
{
|
|
PlayerId = playerId,
|
|
SchemaVersion = PlayerInventorySnapshotResponse.CurrentSchemaVersion,
|
|
BagSlots = MapSlots(snapshot.BagSlots),
|
|
EquipmentSlots = MapSlots(snapshot.EquipmentSlots),
|
|
};
|
|
|
|
private static InventorySlotStateJson[] MapSlots(InventorySlotState[] slots)
|
|
{
|
|
var rows = new InventorySlotStateJson[slots.Length];
|
|
for (var i = 0; i < slots.Length; i++)
|
|
{
|
|
var slot = slots[i];
|
|
rows[i] = slot.IsEmpty
|
|
? new InventorySlotStateJson { SlotIndex = slot.SlotIndex, Quantity = 0 }
|
|
: new InventorySlotStateJson
|
|
{
|
|
SlotIndex = slot.SlotIndex,
|
|
ItemId = slot.ItemId,
|
|
Quantity = slot.Quantity,
|
|
};
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
private static PlayerInventoryMutationResponse BuildMutationResponse(
|
|
string playerId,
|
|
bool applied,
|
|
string? reasonCode,
|
|
PlayerInventorySnapshot? snapshot,
|
|
IPlayerInventoryStore store)
|
|
{
|
|
if (snapshot is null && !store.TryGetSnapshot(playerId, out snapshot))
|
|
{
|
|
snapshot = PlayerInventorySnapshot.Empty();
|
|
}
|
|
|
|
return new PlayerInventoryMutationResponse
|
|
{
|
|
Applied = applied,
|
|
ReasonCode = reasonCode,
|
|
Inventory = MapSnapshot(playerId, snapshot!),
|
|
};
|
|
}
|
|
|
|
private static bool TryParseMutationKind(string? raw, out bool isAdd)
|
|
{
|
|
if (string.Equals(raw, "add", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
isAdd = true;
|
|
return true;
|
|
}
|
|
|
|
if (string.Equals(raw, "remove", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
isAdd = false;
|
|
return true;
|
|
}
|
|
|
|
isAdd = false;
|
|
return false;
|
|
}
|
|
}
|