246 lines
11 KiB
C#
246 lines
11 KiB
C#
using NeonSprawl.Server.Game.Gathering;
|
|
using NeonSprawl.Server.Game.Interaction;
|
|
using NeonSprawl.Server.Game.Items;
|
|
using NeonSprawl.Server.Game.PositionState;
|
|
|
|
namespace NeonSprawl.Server.Tests.Game.Interaction;
|
|
|
|
/// <summary>NEO-63: <c>resource_node</c> interact delegates to <see cref="GatherOperations"/>.</summary>
|
|
public sealed class InteractionResourceNodeGatherIntegrationTests
|
|
{
|
|
[Fact]
|
|
public async Task PostInteract_ResourceNode_WhenInRange_ShouldGrantInventoryAndSalvageXp()
|
|
{
|
|
// Arrange — node at (12, -6), radius 3; stand at (10, -6) → horizontal distance 2
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
await MoveNearAlphaAsync(client);
|
|
|
|
// Act
|
|
var interact = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/interact",
|
|
new InteractionRequest
|
|
{
|
|
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
|
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
|
|
});
|
|
var inventory = await client.GetAsync("/game/players/dev-local-1/inventory");
|
|
var progression = await client.GetAsync("/game/players/dev-local-1/skill-progression");
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, interact.StatusCode);
|
|
var envelope = await interact.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.True(envelope!.Allowed);
|
|
Assert.Equal(PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId, envelope.InteractableId);
|
|
|
|
Assert.Equal(HttpStatusCode.OK, inventory.StatusCode);
|
|
var bag = await inventory.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
|
|
Assert.NotNull(bag);
|
|
Assert.Equal(1, CountItem(bag!, "scrap_metal_bulk"));
|
|
|
|
Assert.Equal(HttpStatusCode.OK, progression.StatusCode);
|
|
var skills = await progression.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>();
|
|
Assert.NotNull(skills);
|
|
var salvage = skills!.Skills!.Single(static s => s.Id == "salvage");
|
|
Assert.Equal(GatherSkillXpConstants.ActivityXpPerResourceNodeGather, salvage.Xp);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostInteract_ResourceNode_WhenDepleted_ShouldDenyWithNodeDepletedWithoutGrants()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
await MoveNearAlphaAsync(client);
|
|
var request = new InteractionRequest
|
|
{
|
|
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
|
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
|
|
};
|
|
|
|
// Act — exhaust capacity, then one more interact
|
|
var successfulGathers = new InteractionResponse[10];
|
|
for (var i = 0; i < successfulGathers.Length; i++)
|
|
{
|
|
var gather = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", request);
|
|
successfulGathers[i] = (await gather.Content.ReadFromJsonAsync<InteractionResponse>())!;
|
|
}
|
|
|
|
var depletedInteract = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", request);
|
|
var inventoryAfter = await client.GetAsync("/game/players/dev-local-1/inventory");
|
|
var progressionAfter = await client.GetAsync("/game/players/dev-local-1/skill-progression");
|
|
|
|
// Assert
|
|
Assert.All(successfulGathers, static r => Assert.True(r.Allowed));
|
|
Assert.Equal(HttpStatusCode.OK, depletedInteract.StatusCode);
|
|
var envelope = await depletedInteract.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Allowed);
|
|
Assert.Equal(GatherReasonCodes.NodeDepleted, envelope.ReasonCode);
|
|
Assert.Equal(10, CountItem(
|
|
(await inventoryAfter.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>())!,
|
|
"scrap_metal_bulk"));
|
|
Assert.Equal(
|
|
10 * GatherSkillXpConstants.ActivityXpPerResourceNodeGather,
|
|
(await progressionAfter.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>())!
|
|
.Skills!.Single(static s => s.Id == "salvage").Xp);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostInteract_ResourceNode_WhenBagFull_ShouldDenyWithInventoryFullWithoutDepletingNode()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
for (var i = 0; i < PlayerInventorySnapshot.BagSlotCount; i++)
|
|
{
|
|
await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/inventory",
|
|
new PlayerInventoryMutationRequest
|
|
{
|
|
SchemaVersion = PlayerInventoryMutationRequest.CurrentSchemaVersion,
|
|
MutationKind = "add",
|
|
ItemId = "survey_drone_kit",
|
|
Quantity = 1,
|
|
});
|
|
}
|
|
|
|
await MoveNearAlphaAsync(client);
|
|
var inventoryBefore = await client.GetAsync("/game/players/dev-local-1/inventory");
|
|
var progressionBefore = await client.GetAsync("/game/players/dev-local-1/skill-progression");
|
|
var scrapBefore = CountItem(
|
|
(await inventoryBefore.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>())!,
|
|
"scrap_metal_bulk");
|
|
var salvageBefore = (await progressionBefore.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>())!
|
|
.Skills!.Single(static s => s.Id == "salvage").Xp;
|
|
|
|
// Act
|
|
var interact = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/interact",
|
|
new InteractionRequest
|
|
{
|
|
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
|
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
|
|
});
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, interact.StatusCode);
|
|
var envelope = await interact.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Allowed);
|
|
Assert.Equal(GatherReasonCodes.InventoryFull, envelope.ReasonCode);
|
|
Assert.Equal(
|
|
scrapBefore,
|
|
CountItem(
|
|
(await (await client.GetAsync("/game/players/dev-local-1/inventory")).Content
|
|
.ReadFromJsonAsync<PlayerInventorySnapshotResponse>())!,
|
|
"scrap_metal_bulk"));
|
|
Assert.Equal(
|
|
salvageBefore,
|
|
(await (await client.GetAsync("/game/players/dev-local-1/skill-progression")).Content
|
|
.ReadFromJsonAsync<SkillProgressionSnapshotResponse>())!
|
|
.Skills!.Single(static s => s.Id == "salvage").Xp);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostInteract_PrototypeTerminal_ShouldNotGrantInventoryOrSalvageXp()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
var move = new MoveCommandRequest
|
|
{
|
|
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
|
Target = new PositionVector { X = 0.5, Y = 0.9, Z = 0.5 },
|
|
};
|
|
await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
|
|
|
// Act
|
|
var interact = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/interact",
|
|
new InteractionRequest
|
|
{
|
|
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
|
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
|
});
|
|
var inventory = await client.GetAsync("/game/players/dev-local-1/inventory");
|
|
var progression = await client.GetAsync("/game/players/dev-local-1/skill-progression");
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, interact.StatusCode);
|
|
var envelope = await interact.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.True(envelope!.Allowed);
|
|
|
|
var bag = await inventory.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
|
|
Assert.NotNull(bag);
|
|
Assert.Equal(0, CountItem(bag!, "scrap_metal_bulk"));
|
|
|
|
var skills = await progression.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>();
|
|
Assert.NotNull(skills);
|
|
var salvage = skills!.Skills!.Single(static s => s.Id == "salvage");
|
|
Assert.Equal(0, salvage.Xp);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PostInteract_ResourceNode_WhenSalvageDisallowsActivity_ShouldDenyWithoutGrants()
|
|
{
|
|
// Arrange
|
|
await using var factory = new SalvageActivityDeniedRegistryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
await MoveNearAlphaAsync(client);
|
|
|
|
// Act
|
|
var interact = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/interact",
|
|
new InteractionRequest
|
|
{
|
|
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
|
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
|
|
});
|
|
var inventory = await client.GetAsync("/game/players/dev-local-1/inventory");
|
|
var progression = await client.GetAsync("/game/players/dev-local-1/skill-progression");
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, interact.StatusCode);
|
|
var envelope = await interact.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Allowed);
|
|
Assert.Equal(SkillProgressionSnapshotApi.ReasonSourceKindNotAllowed, envelope.ReasonCode);
|
|
|
|
var bag = await inventory.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
|
|
Assert.NotNull(bag);
|
|
Assert.Equal(0, CountItem(bag!, "scrap_metal_bulk"));
|
|
|
|
var skills = await progression.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>();
|
|
Assert.NotNull(skills);
|
|
var salvage = skills!.Skills!.Single(static s => s.Id == "salvage");
|
|
Assert.Equal(0, salvage.Xp);
|
|
}
|
|
|
|
private static async Task MoveNearAlphaAsync(HttpClient client)
|
|
{
|
|
var move = new MoveCommandRequest
|
|
{
|
|
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
|
Target = new PositionVector { X = 10, Y = 0.9, Z = -6 },
|
|
};
|
|
await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
|
}
|
|
|
|
private static int CountItem(PlayerInventorySnapshotResponse snapshot, string itemId)
|
|
{
|
|
var total = 0;
|
|
foreach (var slot in snapshot.BagSlots!)
|
|
{
|
|
if (string.Equals(slot.ItemId, itemId, StringComparison.Ordinal))
|
|
{
|
|
total += slot.Quantity;
|
|
}
|
|
}
|
|
|
|
return total;
|
|
}
|
|
}
|