687 lines
25 KiB
C#
687 lines
25 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using NeonSprawl.Server.Game.Factions;
|
|
using NeonSprawl.Server.Game.Items;
|
|
using NeonSprawl.Server.Game.Mastery;
|
|
using NeonSprawl.Server.Game.PositionState;
|
|
using NeonSprawl.Server.Game.Quests;
|
|
using NeonSprawl.Server.Game.Rewards;
|
|
using NeonSprawl.Server.Game.Skills;
|
|
using NeonSprawl.Server.Tests;
|
|
using Xunit;
|
|
|
|
namespace NeonSprawl.Server.Tests.Game.Quests;
|
|
|
|
public sealed class QuestStateOperationsTests
|
|
{
|
|
private const string PlayerId = "dev-local-1";
|
|
private const string UnknownPlayerId = "unknown-player-xyz";
|
|
private const string GatherQuestId = PrototypeE7M1QuestCatalogRules.GatherIntroQuestId;
|
|
private const string RefineQuestId = PrototypeE7M1QuestCatalogRules.RefineIntroQuestId;
|
|
private const string CombatQuestId = PrototypeE7M1QuestCatalogRules.CombatIntroQuestId;
|
|
private const string ChainQuestId = PrototypeE7M1QuestCatalogRules.ChainQuestId;
|
|
private const string ChainObjectiveId = PrototypeE7M1QuestCatalogRules.ChainFirstObjectiveId;
|
|
private const string GridContractQuestId = PrototypeE7M1QuestCatalogRules.GridContractQuestId;
|
|
private const string GridFactionId = PrototypeE7M3QuestFactionRules.GridContractGateFactionId;
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldDenyFactionGateBlocked_WhenGridContractBelowThreshold()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
CompleteChainPrerequisite(deps);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, GridContractQuestId);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.FactionGateBlocked, result.ReasonCode);
|
|
Assert.Null(result.Snapshot);
|
|
Assert.False(deps.ProgressStore.TryGetProgress(PlayerId, GridContractQuestId, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldAcceptGridContract_WhenStandingAtThreshold()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
CompleteChainPrerequisite(deps);
|
|
SeedGridStanding(deps, PrototypeE7M3QuestFactionRules.GridContractMinStanding);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, GridContractQuestId);
|
|
|
|
// Assert
|
|
Assert.True(result.Success);
|
|
Assert.Null(result.ReasonCode);
|
|
Assert.NotNull(result.Snapshot);
|
|
Assert.Equal(QuestProgressStatus.Active, result.Snapshot!.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldDenyFactionGateBlocked_WhenStandingOneBelowThreshold()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
CompleteChainPrerequisite(deps);
|
|
SeedGridStanding(deps, PrototypeE7M3QuestFactionRules.GridContractMinStanding - 1);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, GridContractQuestId);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.FactionGateBlocked, result.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldActivateGatherIntro_WhenNotStarted()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, GatherQuestId);
|
|
|
|
// Assert
|
|
Assert.True(result.Success);
|
|
Assert.Null(result.ReasonCode);
|
|
Assert.NotNull(result.Snapshot);
|
|
Assert.Equal(QuestProgressStatus.Active, result.Snapshot!.Status);
|
|
Assert.Equal(0, result.Snapshot.CurrentStepIndex);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldDenyPrerequisiteIncomplete_WhenRefineIntroBeforeGatherComplete()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, RefineQuestId);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.PrerequisiteIncomplete, result.ReasonCode);
|
|
Assert.Null(result.Snapshot);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldSucceedRefineIntro_WhenGatherIntroCompleted()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
var timeProvider = TimeProvider.System;
|
|
Assert.True(TryAccept(deps, GatherQuestId).Success);
|
|
Assert.True(TryMarkComplete(deps, GatherQuestId).Success);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, RefineQuestId);
|
|
|
|
// Assert
|
|
Assert.True(result.Success);
|
|
Assert.Null(result.ReasonCode);
|
|
Assert.Equal(QuestProgressStatus.Active, result.Snapshot!.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldDenyUnknownQuest_WhenQuestIdNotInCatalog()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, "prototype_quest_missing");
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.UnknownQuest, result.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldDenyAlreadyActive_WhenQuestAlreadyAccepted()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
Assert.True(TryAccept(deps, GatherQuestId).Success);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, GatherQuestId);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.AlreadyActive, result.ReasonCode);
|
|
Assert.NotNull(result.Snapshot);
|
|
Assert.Equal(QuestProgressStatus.Active, result.Snapshot!.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldDenyAlreadyCompleted_WhenQuestAlreadyCompleted()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
var timeProvider = TimeProvider.System;
|
|
Assert.True(TryAccept(deps, GatherQuestId).Success);
|
|
Assert.True(TryMarkComplete(deps, GatherQuestId).Success);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, GatherQuestId);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.AlreadyCompleted, result.ReasonCode);
|
|
Assert.Equal(QuestProgressStatus.Completed, result.Snapshot!.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldDenyUnknownPlayer_WhenPrerequisiteQuestNotWritable()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, UnknownPlayerId, RefineQuestId);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.UnknownPlayer, result.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldDenyUnknownPlayer_WhenPlayerNotInStore()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, UnknownPlayerId, GatherQuestId);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.UnknownPlayer, result.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAdvanceStep_ShouldAdvanceChainQuestAndClearCounters()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
var timeProvider = TimeProvider.System;
|
|
AcceptAndComplete(deps, GatherQuestId);
|
|
AcceptAndComplete(deps, RefineQuestId);
|
|
AcceptAndComplete(deps, CombatQuestId);
|
|
Assert.True(TryAccept(deps, ChainQuestId).Success);
|
|
Assert.True(deps.ProgressStore.TryUpdateObjectiveCounter(PlayerId, ChainQuestId, ChainObjectiveId, 5, out _));
|
|
|
|
// Act
|
|
var result = TryAdvanceStep(deps, ChainQuestId, 1);
|
|
|
|
// Assert
|
|
Assert.True(result.Success);
|
|
Assert.Null(result.ReasonCode);
|
|
Assert.Equal(1, result.Snapshot!.CurrentStepIndex);
|
|
Assert.Empty(result.Snapshot.ObjectiveCounters);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(4)]
|
|
public async Task TryAdvanceStep_ShouldDenyInvalidStepIndex_WhenIndexNotIncreasingOrPastLastStep(int newStepIndex)
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
var timeProvider = TimeProvider.System;
|
|
AcceptAndComplete(deps, GatherQuestId);
|
|
AcceptAndComplete(deps, RefineQuestId);
|
|
AcceptAndComplete(deps, CombatQuestId);
|
|
Assert.True(TryAccept(deps, ChainQuestId).Success);
|
|
|
|
// Act
|
|
var result = TryAdvanceStep(deps, ChainQuestId, newStepIndex);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.InvalidStepIndex, result.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAdvanceStep_ShouldDenyUnknownQuest_WhenQuestIdNotInCatalog()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
|
|
// Act
|
|
var result = TryAdvanceStep(deps, "prototype_quest_missing", 1);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.UnknownQuest, result.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryMarkComplete_ShouldDenyUnknownQuest_WhenQuestIdNotInCatalog()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
|
|
// Act
|
|
var result = TryMarkComplete(deps, "prototype_quest_missing");
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.UnknownQuest, result.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAdvanceStep_ShouldDenyNotActive_WhenQuestNotAccepted()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
|
|
// Act
|
|
var result = TryAdvanceStep(deps, GatherQuestId, 1);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.NotActive, result.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAdvanceStep_ShouldDenyAlreadyCompleted_WhenQuestCompleted()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
var timeProvider = TimeProvider.System;
|
|
Assert.True(TryAccept(deps, GatherQuestId).Success);
|
|
Assert.True(TryMarkComplete(deps, GatherQuestId).Success);
|
|
|
|
// Act
|
|
var result = TryAdvanceStep(deps, GatherQuestId, 1);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.AlreadyCompleted, result.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryMarkComplete_ShouldDeliverGatherIntroBundle_WhenFirstCompletion()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
Assert.True(TryAccept(deps, GatherQuestId).Success);
|
|
|
|
// Act
|
|
var result = TryMarkComplete(deps, GatherQuestId);
|
|
|
|
// Assert
|
|
Assert.True(result.Success);
|
|
Assert.Null(result.ReasonCode);
|
|
Assert.Equal(QuestProgressStatus.Completed, result.Snapshot!.Status);
|
|
Assert.True(deps.DeliveryStore.TryGet(PlayerId, GatherQuestId, out var deliveryEvent));
|
|
Assert.Equal(25, deps.SkillProgressionStore.GetXpTotals(PlayerId).GetValueOrDefault("salvage"));
|
|
Assert.Single(deliveryEvent.GrantedSkillXp);
|
|
Assert.Equal("salvage", deliveryEvent.GrantedSkillXp[0].SkillId);
|
|
Assert.Equal(25, deliveryEvent.GrantedSkillXp[0].Amount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryMarkComplete_ShouldNotDoubleGrant_WhenIdempotentReplay()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
Assert.True(TryAccept(deps, GatherQuestId).Success);
|
|
Assert.True(TryMarkComplete(deps, GatherQuestId).Success);
|
|
Assert.True(deps.DeliveryStore.TryGet(PlayerId, GatherQuestId, out var firstEvent));
|
|
|
|
// Act
|
|
var replay = TryMarkComplete(deps, GatherQuestId);
|
|
|
|
// Assert
|
|
Assert.True(replay.Success);
|
|
Assert.Equal(25, deps.SkillProgressionStore.GetXpTotals(PlayerId).GetValueOrDefault("salvage"));
|
|
Assert.True(deps.DeliveryStore.TryGet(PlayerId, GatherQuestId, out var replayEvent));
|
|
Assert.Equal(firstEvent.IdempotencyKey, replayEvent.IdempotencyKey);
|
|
Assert.Equal(firstEvent.DeliveredAt, replayEvent.DeliveredAt);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryMarkComplete_ShouldDeliverOperatorChainBundle_WhenFirstCompletion()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
AcceptAndComplete(deps, GatherQuestId);
|
|
AcceptAndComplete(deps, RefineQuestId);
|
|
AcceptAndComplete(deps, CombatQuestId);
|
|
Assert.True(TryAccept(deps, ChainQuestId).Success);
|
|
deps.InventoryStore.TryGetSnapshot(PlayerId, out var beforeInventory);
|
|
|
|
// Act
|
|
var result = TryMarkComplete(deps, ChainQuestId);
|
|
|
|
// Assert
|
|
Assert.True(result.Success);
|
|
Assert.Null(result.ReasonCode);
|
|
Assert.Equal(QuestProgressStatus.Completed, result.Snapshot!.Status);
|
|
Assert.True(deps.DeliveryStore.TryGet(PlayerId, ChainQuestId, out var deliveryEvent));
|
|
Assert.Single(deliveryEvent.GrantedItems);
|
|
Assert.Equal("survey_drone_kit", deliveryEvent.GrantedItems[0].ItemId);
|
|
Assert.Equal(1, deliveryEvent.GrantedItems[0].Quantity);
|
|
Assert.Single(deliveryEvent.GrantedSkillXp);
|
|
Assert.Equal("salvage", deliveryEvent.GrantedSkillXp[0].SkillId);
|
|
Assert.Equal(50, deliveryEvent.GrantedSkillXp[0].Amount);
|
|
Assert.Single(deliveryEvent.GrantedReputation);
|
|
Assert.Equal(GridFactionId, deliveryEvent.GrantedReputation[0].FactionId);
|
|
Assert.Equal(15, deliveryEvent.GrantedReputation[0].Amount);
|
|
deps.InventoryStore.TryGetSnapshot(PlayerId, out var afterInventory);
|
|
Assert.Equal(CountBagItem(beforeInventory!, "survey_drone_kit") + 1, CountBagItem(afterInventory!, "survey_drone_kit"));
|
|
Assert.Equal(100, deps.SkillProgressionStore.GetXpTotals(PlayerId).GetValueOrDefault("salvage"));
|
|
Assert.Equal(
|
|
PrototypeE7M3QuestFactionRules.GridContractMinStanding,
|
|
deps.StandingStore.TryGetStanding(PlayerId, GridFactionId).Standing);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryAccept_ShouldAcceptGridContract_WhenOperatorChainCompletedWithRepGrant()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
AcceptAndComplete(deps, GatherQuestId);
|
|
AcceptAndComplete(deps, RefineQuestId);
|
|
AcceptAndComplete(deps, CombatQuestId);
|
|
Assert.True(TryAccept(deps, ChainQuestId).Success);
|
|
Assert.True(TryMarkComplete(deps, ChainQuestId).Success);
|
|
|
|
// Act
|
|
var result = TryAccept(deps, GridContractQuestId);
|
|
|
|
// Assert
|
|
Assert.True(result.Success);
|
|
Assert.Null(result.ReasonCode);
|
|
Assert.NotNull(result.Snapshot);
|
|
Assert.Equal(QuestProgressStatus.Completed, result.Snapshot!.Status);
|
|
Assert.Equal(
|
|
PrototypeE7M3QuestFactionRules.GridContractMinStanding,
|
|
deps.StandingStore.TryGetStanding(PlayerId, GridFactionId).Standing);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryMarkComplete_ShouldDenyAndStayActive_WhenInventoryFull()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
AcceptAndComplete(deps, GatherQuestId);
|
|
AcceptAndComplete(deps, RefineQuestId);
|
|
AcceptAndComplete(deps, CombatQuestId);
|
|
Assert.True(TryAccept(deps, ChainQuestId).Success);
|
|
FillBag(deps);
|
|
|
|
// Act
|
|
var result = TryMarkComplete(deps, ChainQuestId);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(RewardDeliveryReasonCodes.InventoryFull, result.ReasonCode);
|
|
Assert.True(deps.ProgressStore.TryGetProgress(PlayerId, ChainQuestId, out var progress));
|
|
Assert.Equal(QuestProgressStatus.Active, progress.Status);
|
|
Assert.False(deps.DeliveryStore.TryGet(PlayerId, ChainQuestId, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryMarkComplete_ShouldBeIdempotentAtOperationsLayer()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
Assert.True(TryAccept(deps, GatherQuestId).Success);
|
|
|
|
// Act
|
|
var first = TryMarkComplete(deps, GatherQuestId);
|
|
var second = TryMarkComplete(deps, GatherQuestId);
|
|
|
|
// Assert
|
|
Assert.True(first.Success);
|
|
Assert.Null(first.ReasonCode);
|
|
Assert.NotNull(first.Snapshot!.CompletedAt);
|
|
Assert.True(second.Success);
|
|
Assert.Null(second.ReasonCode);
|
|
Assert.Equal(first.Snapshot.CompletedAt, second.Snapshot!.CompletedAt);
|
|
Assert.Equal(QuestProgressStatus.Completed, second.Snapshot.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TryMarkComplete_ShouldDenyNotActive_WhenQuestNeverAccepted()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
|
|
// Act
|
|
var result = TryMarkComplete(deps, GatherQuestId);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal(QuestStateReasonCodes.NotActive, result.ReasonCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Host_ShouldResolveRegistryAndStore_ForQuestStateOperations()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var deps = ResolveDependencies(factory);
|
|
|
|
// Act
|
|
var accept = TryAccept(deps, GatherQuestId);
|
|
var complete = TryMarkComplete(deps, GatherQuestId);
|
|
|
|
// Assert
|
|
Assert.True(accept.Success);
|
|
Assert.True(complete.Success);
|
|
Assert.Equal(QuestProgressStatus.Completed, complete.Snapshot!.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanWritePlayer_ShouldReturnFalse_ForUnknownPlayer()
|
|
{
|
|
// Arrange
|
|
var store = new InMemoryPlayerQuestStateStore(
|
|
Microsoft.Extensions.Options.Options.Create(new GamePositionOptions { DevPlayerId = PlayerId }));
|
|
// Act
|
|
var canWrite = store.CanWritePlayer(UnknownPlayerId);
|
|
// Assert
|
|
Assert.False(canWrite);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanWritePlayer_ShouldReturnTrue_ForDevPlayer()
|
|
{
|
|
// Arrange
|
|
var store = new InMemoryPlayerQuestStateStore(
|
|
Microsoft.Extensions.Options.Options.Create(new GamePositionOptions { DevPlayerId = PlayerId }));
|
|
// Act
|
|
var canWrite = store.CanWritePlayer(PlayerId);
|
|
// Assert
|
|
Assert.True(canWrite);
|
|
}
|
|
|
|
private static void CompleteChainPrerequisite(QuestTestDependencies deps)
|
|
{
|
|
Assert.True(deps.ProgressStore.TryActivate(PlayerId, ChainQuestId, out _));
|
|
Assert.True(deps.ProgressStore.TryMarkComplete(
|
|
PlayerId,
|
|
ChainQuestId,
|
|
DateTimeOffset.UtcNow,
|
|
out _));
|
|
}
|
|
|
|
private static void SeedGridStanding(QuestTestDependencies deps, int standing)
|
|
{
|
|
if (standing == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var auditStore = deps.ReputationDeltaStore;
|
|
var outcome = ReputationOperations.TryApplyDelta(
|
|
PlayerId,
|
|
GridFactionId,
|
|
standing,
|
|
$"seed-{standing}",
|
|
ReputationDeltaSourceKinds.QuestCompletion,
|
|
ChainQuestId,
|
|
deps.StandingStore,
|
|
auditStore,
|
|
deps.TimeProvider);
|
|
Assert.True(outcome.Success);
|
|
Assert.Equal(standing, deps.StandingStore.TryGetStanding(PlayerId, GridFactionId).Standing);
|
|
}
|
|
|
|
private static int CountBagItem(PlayerInventorySnapshot 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;
|
|
}
|
|
|
|
private static void FillBag(QuestTestDependencies deps)
|
|
{
|
|
for (var i = 0; i < PlayerInventorySnapshot.BagSlotCount; i++)
|
|
{
|
|
var add = PlayerInventoryOperations.TryAddStack(
|
|
PlayerId,
|
|
"survey_drone_kit",
|
|
quantity: 1,
|
|
deps.ItemRegistry,
|
|
deps.InventoryStore);
|
|
Assert.Equal(PlayerInventoryMutationKind.Applied, add.Kind);
|
|
}
|
|
}
|
|
|
|
private static void AcceptAndComplete(QuestTestDependencies deps, string questId)
|
|
{
|
|
Assert.True(TryAccept(deps, questId).Success);
|
|
Assert.True(TryMarkComplete(deps, questId).Success);
|
|
}
|
|
|
|
private static QuestStateOperationResult TryMarkComplete(QuestTestDependencies deps, string questId) =>
|
|
QuestStateOperations.TryMarkComplete(
|
|
PlayerId,
|
|
questId,
|
|
deps.QuestRegistry,
|
|
deps.ProgressStore,
|
|
deps.ItemRegistry,
|
|
deps.InventoryStore,
|
|
deps.SkillRegistry,
|
|
deps.SkillProgressionStore,
|
|
deps.LevelCurve,
|
|
deps.PerkUnlockEngine,
|
|
deps.PerkStore,
|
|
deps.DeliveryStore,
|
|
deps.StandingStore,
|
|
deps.ReputationDeltaStore,
|
|
deps.TimeProvider);
|
|
|
|
private static QuestStateOperationResult TryAccept(QuestTestDependencies deps, string questId) =>
|
|
TryAccept(deps, PlayerId, questId);
|
|
|
|
private static QuestStateOperationResult TryAccept(QuestTestDependencies deps, string playerId, string questId) =>
|
|
QuestStateOperations.TryAccept(
|
|
playerId,
|
|
questId,
|
|
deps.QuestRegistry,
|
|
deps.ProgressStore,
|
|
deps.InventoryStore,
|
|
deps.ItemRegistry,
|
|
deps.SkillRegistry,
|
|
deps.SkillProgressionStore,
|
|
deps.LevelCurve,
|
|
deps.PerkUnlockEngine,
|
|
deps.PerkStore,
|
|
deps.DeliveryStore,
|
|
deps.StandingStore,
|
|
deps.ReputationDeltaStore,
|
|
deps.TimeProvider);
|
|
|
|
private static QuestStateOperationResult TryAdvanceStep(QuestTestDependencies deps, string questId, int newStepIndex) =>
|
|
QuestStateOperations.TryAdvanceStep(
|
|
PlayerId,
|
|
questId,
|
|
newStepIndex,
|
|
deps.QuestRegistry,
|
|
deps.ProgressStore,
|
|
deps.InventoryStore,
|
|
deps.ItemRegistry,
|
|
deps.SkillRegistry,
|
|
deps.SkillProgressionStore,
|
|
deps.LevelCurve,
|
|
deps.PerkUnlockEngine,
|
|
deps.PerkStore,
|
|
deps.DeliveryStore,
|
|
deps.StandingStore,
|
|
deps.ReputationDeltaStore,
|
|
deps.TimeProvider);
|
|
|
|
private static QuestTestDependencies ResolveDependencies(InMemoryWebApplicationFactory factory)
|
|
{
|
|
var services = factory.Services;
|
|
return new QuestTestDependencies(
|
|
services.GetRequiredService<IQuestDefinitionRegistry>(),
|
|
services.GetRequiredService<IPlayerQuestStateStore>(),
|
|
services.GetRequiredService<IPlayerInventoryStore>(),
|
|
services.GetRequiredService<IItemDefinitionRegistry>(),
|
|
services.GetRequiredService<ISkillDefinitionRegistry>(),
|
|
services.GetRequiredService<IPlayerSkillProgressionStore>(),
|
|
services.GetRequiredService<ISkillLevelCurve>(),
|
|
services.GetRequiredService<PerkUnlockEngine>(),
|
|
services.GetRequiredService<IPlayerPerkStateStore>(),
|
|
services.GetRequiredService<IRewardDeliveryStore>(),
|
|
services.GetRequiredService<IFactionStandingStore>(),
|
|
services.GetRequiredService<IReputationDeltaStore>(),
|
|
TimeProvider.System);
|
|
}
|
|
|
|
private readonly record struct QuestTestDependencies(
|
|
IQuestDefinitionRegistry QuestRegistry,
|
|
IPlayerQuestStateStore ProgressStore,
|
|
IPlayerInventoryStore InventoryStore,
|
|
IItemDefinitionRegistry ItemRegistry,
|
|
ISkillDefinitionRegistry SkillRegistry,
|
|
IPlayerSkillProgressionStore SkillProgressionStore,
|
|
ISkillLevelCurve LevelCurve,
|
|
PerkUnlockEngine PerkUnlockEngine,
|
|
IPlayerPerkStateStore PerkStore,
|
|
IRewardDeliveryStore DeliveryStore,
|
|
IFactionStandingStore StandingStore,
|
|
IReputationDeltaStore ReputationDeltaStore,
|
|
TimeProvider TimeProvider);
|
|
}
|