210 lines
9.1 KiB
C#
210 lines
9.1 KiB
C#
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using NeonSprawl.Server.Game.Gathering;
|
|
using NeonSprawl.Server.Game.Interaction;
|
|
using NeonSprawl.Server.Game.Items;
|
|
using NeonSprawl.Server.Game.PositionState;
|
|
using NeonSprawl.Server.Game.Skills;
|
|
using NeonSprawl.Server.Game.World;
|
|
using Xunit;
|
|
|
|
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);
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
var gather = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/interact",
|
|
new InteractionRequest
|
|
{
|
|
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
|
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
|
|
});
|
|
Assert.Equal(HttpStatusCode.OK, gather.StatusCode);
|
|
var body = await gather.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
Assert.NotNull(body);
|
|
Assert.True(body!.Allowed);
|
|
}
|
|
|
|
var inventoryBefore = await client.GetAsync("/game/players/dev-local-1/inventory");
|
|
var progressionBefore = await client.GetAsync("/game/players/dev-local-1/skill-progression");
|
|
var bagBefore = await inventoryBefore.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
|
|
var skillsBefore = await progressionBefore.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>();
|
|
var scrapBefore = CountItem(bagBefore!, "scrap_metal_bulk");
|
|
var salvageBefore = skillsBefore!.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,
|
|
});
|
|
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.Equal(HttpStatusCode.OK, interact.StatusCode);
|
|
var envelope = await interact.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
Assert.NotNull(envelope);
|
|
Assert.False(envelope!.Allowed);
|
|
Assert.Equal(GatherReasonCodes.NodeDepleted, envelope.ReasonCode);
|
|
|
|
var bagAfter = await inventoryAfter.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
|
|
var skillsAfter = await progressionAfter.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>();
|
|
Assert.Equal(scrapBefore, CountItem(bagAfter!, "scrap_metal_bulk"));
|
|
Assert.Equal(
|
|
salvageBefore,
|
|
skillsAfter!.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.False(string.IsNullOrEmpty(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;
|
|
}
|
|
}
|