using System;
using System.Collections.Generic;
using System.Linq;
using AcDream.Core.Net.Messages;
using AcDream.Core.Quests;
using AcDream.Plugin.Abstractions;
using AcDream.Runtime.Gameplay;
namespace AcDream.Runtime.Tests.Gameplay;
///
/// Campaign QT slice QT6: what a plugin sees of the contract tracker.
///
public sealed class ContractPluginProjectionTests
{
private static readonly DateTime Now = new(2026, 8, 21, 12, 0, 0, DateTimeKind.Utc);
private static void Track(
RuntimeContractState state,
uint contractId,
uint stage,
bool setAsDisplay = false)
=> state.ApplyUpdate(new ContractTrackerUpdate(
new ContractTracker(1u, contractId, (ContractStage)stage, 0d, 0d, Now),
Delete: false,
SetAsDisplay: setAsDisplay));
private static ContractCatalog Catalog(uint id, string name, string progressFormat = "")
=> new(new Dictionary
{
[id] = ContractEntry.Unknown with
{
ContractId = id,
ContractName = name,
Description = "Do the thing.",
DescriptionProgress = progressFormat,
},
});
[Fact]
public void AnEmptyTrackerProjectsToNothing()
{
using var state = new RuntimeContractState();
Assert.Empty(ContractPluginProjection.Project(state.View, ContractCatalog.Empty, Now));
}
[Fact]
public void TheProjectionCarriesTheAuthoredTextAndTheRetailStatus()
{
using var state = new RuntimeContractState();
Track(state, 0x10u, stage: 9u); // ProgressCounter + 5
ContractSnapshot snapshot = Assert.Single(ContractPluginProjection.Project(
state.View, Catalog(0x10u, "Tusker Hunt", "%d/20 Tuskers"), Now));
Assert.Equal(0x10u, snapshot.ContractId);
Assert.Equal(9u, snapshot.Stage);
Assert.Equal(5u, snapshot.Progress);
Assert.Equal("Tusker Hunt", snapshot.Name);
Assert.Equal("Do the thing.", snapshot.Description);
Assert.Equal("5/20 Tuskers", snapshot.Status);
}
[Fact]
public void TheDisplayContractIsFlagged()
{
using var state = new RuntimeContractState();
Track(state, 0x10u, stage: 2u);
Track(state, 0x20u, stage: 2u, setAsDisplay: true);
IReadOnlyList projected = ContractPluginProjection.Project(
state.View, ContractCatalog.Empty, Now);
Assert.False(projected.Single(c => c.ContractId == 0x10u).IsDisplayed);
Assert.True(projected.Single(c => c.ContractId == 0x20u).IsDisplayed);
}
[Fact]
public void WithNoCatalogTheNumbersStillProject()
{
// A headless bot has no dat access. Losing the text is expected;
// losing the QUEST would mean a bot silently unable to see what it is
// on, which is the failure this rules out.
using var state = new RuntimeContractState();
Track(state, 0x10u, stage: 6u);
ContractSnapshot snapshot = Assert.Single(
ContractPluginProjection.Project(state.View, catalog: null, Now));
Assert.Equal(0x10u, snapshot.ContractId);
Assert.Equal(6u, snapshot.Stage);
Assert.Equal(2u, snapshot.Progress);
Assert.Equal(string.Empty, snapshot.Name);
Assert.Equal(string.Empty, snapshot.Status);
}
[Fact]
public void AContractTheCatalogDoesNotKnowStillProjects()
{
// Same rule as the panel: the server can track a contract this dat
// build has never heard of, and a plugin must not miss it.
using var state = new RuntimeContractState();
Track(state, 0xDEADu, stage: 2u);
ContractSnapshot snapshot = Assert.Single(ContractPluginProjection.Project(
state.View, Catalog(0x10u, "Something Else"), Now));
Assert.Equal(0xDEADu, snapshot.ContractId);
Assert.Equal(string.Empty, snapshot.Name);
// ContractEntry.Unknown still runs the progress arms, so an in-progress
// contract reads correctly even with no authored text.
Assert.Equal("In Progress", snapshot.Status);
}
[Fact]
public void TheProjectionOrderMatchesTheTrackersOwn()
{
using var state = new RuntimeContractState();
Track(state, 0x30u, stage: 2u);
Track(state, 0x10u, stage: 2u);
Track(state, 0x20u, stage: 2u);
IReadOnlyList projected = ContractPluginProjection.Project(
state.View, ContractCatalog.Empty, Now);
Assert.Equal(
new uint[] { 0x10u, 0x20u, 0x30u },
projected.Select(c => c.ContractId).ToArray());
}
}