319 lines
11 KiB
C#
319 lines
11 KiB
C#
using System;
|
|
using System.Linq;
|
|
using CultMath;
|
|
using GameCult.Caching;
|
|
using UniRx;
|
|
using Xunit;
|
|
|
|
// A side's brain reaches other ships only through its PilotView (Agents/PilotView.cs): handles it issued, tracks only
|
|
// of what a member tracks, picture only of what a member's sensors hold hostile. Driven on the IffAndCombatTests world,
|
|
// like FlightTests; perception is set directly, a grudge is a real IFF override.
|
|
public sealed class PilotViewTests : IDisposable
|
|
{
|
|
private readonly IffAndCombatTests _world = new IffAndCombatTests();
|
|
private readonly ItemManager _items;
|
|
private readonly Zone _zone;
|
|
|
|
public PilotViewTests() => (_items, _zone) = _world.BuildWorld();
|
|
|
|
public void Dispose() => _world.Dispose();
|
|
|
|
private Ship Pilot(Faction faction, float x = 0, float z = 0)
|
|
{
|
|
var ship = _world.NewShip(_items, _zone, faction, piloted: true);
|
|
ship.Position = new float3(x, 0, z);
|
|
return ship;
|
|
}
|
|
|
|
private Ship Subject(float x = 100, float z = 0)
|
|
{
|
|
var ship = _world.NewShip(_items, _zone, new Faction { Name = "Other" });
|
|
ship.Position = new float3(x, 0, z);
|
|
return ship;
|
|
}
|
|
|
|
private static void See(Ship member, Entity subject)
|
|
{
|
|
member.EntityInfoGathered[subject] = 1;
|
|
if (!member.VisibleEntities.Contains(subject)) member.VisibleEntities.Add(subject);
|
|
member.VisibleEnemies.Add(subject);
|
|
}
|
|
|
|
private PilotView ViewOf(Ship ship) => _zone.Agents.Single(a => a.Ship == ship).View;
|
|
|
|
private void Tick(float dt)
|
|
{
|
|
foreach (var flight in _zone.Flights.ToArray()) flight.Update(dt);
|
|
foreach (var entity in _zone.Entities.ToArray()) PilotApplier.Apply(entity);
|
|
}
|
|
|
|
[Fact]
|
|
public void ASideNeverListsItsOwnMember()
|
|
{
|
|
var a = Pilot(null, -200);
|
|
var b = Pilot(null, 200);
|
|
a.SetIff(b, true);
|
|
b.SetIff(a, true);
|
|
See(a, b);
|
|
See(b, a);
|
|
|
|
Assert.Equal(2, _zone.Flights.Count());
|
|
Assert.Equal(new[] { PilotView.Handle(b) }, ViewOf(a).Picture());
|
|
Assert.Equal(new[] { PilotView.Handle(a) }, ViewOf(b).Picture());
|
|
for (var step = 0; step < 600; step++)
|
|
{
|
|
Tick(.1f);
|
|
foreach (var member in new[] { a, b })
|
|
Assert.NotEqual(PilotView.Handle(member), (_zone.Agents.Single(x => x.Ship == member).Task as FollowTask)?.Anchor);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void AFactionsPictureLeavesOutItsOwnMembers()
|
|
{
|
|
var faction = new Faction { Name = "Squabblers" };
|
|
var a = Pilot(faction, -200);
|
|
var b = Pilot(faction, 200);
|
|
var other = Subject();
|
|
See(a, b);
|
|
See(b, a);
|
|
See(b, other);
|
|
|
|
Assert.Equal(new[] { PilotView.Handle(other) }, ViewOf(a).Picture());
|
|
}
|
|
|
|
[Fact]
|
|
public void APictureIsTheMembersEnemiesInMemberOrderEachOnce()
|
|
{
|
|
var faction = new Faction { Name = "Plain" };
|
|
var a = Pilot(faction, -10);
|
|
var b = Pilot(faction, 10);
|
|
var x = Subject(100);
|
|
var y = Subject(200);
|
|
var z = Subject(300);
|
|
See(a, x);
|
|
See(a, y);
|
|
See(b, y);
|
|
See(b, z);
|
|
|
|
Assert.Equal(new[] { x, y, z }.Select(PilotView.Handle), ViewOf(a).Picture());
|
|
}
|
|
|
|
[Fact]
|
|
public void ATrackIsOnlyWhatTheMemberTracks()
|
|
{
|
|
var member = Pilot(null);
|
|
var subject = Subject(120, 30);
|
|
subject.Velocity = new float2(3, -4);
|
|
subject.Direction = new float2(0, -1);
|
|
var threshold = _items.GameplaySettings.TargetDetectionInfoThreshold;
|
|
var view = ViewOf(member);
|
|
var handle = PilotView.Handle(subject);
|
|
|
|
member.EntityInfoGathered[subject] = threshold / 2;
|
|
Assert.Null(view.TrackOf(member, handle));
|
|
Assert.Equal(threshold / 2, view.Info(member, handle));
|
|
|
|
member.EntityInfoGathered[subject] = threshold * 2;
|
|
var track = view.TrackOf(member, handle);
|
|
Assert.NotNull(track);
|
|
Assert.Equal(subject.Position, track.Value.Position);
|
|
Assert.Equal(subject.Velocity, track.Value.Velocity);
|
|
Assert.Equal(subject.Direction, track.Value.Direction);
|
|
Assert.Equal(threshold * 2, view.Info(member, handle));
|
|
}
|
|
|
|
[Fact]
|
|
public void ASideTrackComesFromATrackingMember()
|
|
{
|
|
var faction = new Faction { Name = "Plain" };
|
|
var a = Pilot(faction, -10);
|
|
var b = Pilot(faction, 10);
|
|
var x = Subject(100);
|
|
var y = Subject(200);
|
|
See(b, x);
|
|
var view = ViewOf(a);
|
|
|
|
Assert.Null(view.TrackOf(a, PilotView.Handle(x)));
|
|
Assert.Equal(x.Position, view.SideTrack(PilotView.Handle(x)).Value.Position);
|
|
Assert.Null(view.SideTrack(PilotView.Handle(y)));
|
|
}
|
|
|
|
[Fact]
|
|
public void AViewAnswersIffOverridesAndTrespassPerMember()
|
|
{
|
|
var member = Pilot(null);
|
|
var subject = Subject();
|
|
var view = ViewOf(member);
|
|
var handle = PilotView.Handle(subject);
|
|
|
|
Assert.Null(view.IffOverride(member, handle));
|
|
member.SetIff(subject, true);
|
|
Assert.True(view.IffOverride(member, handle));
|
|
member.SetIff(subject, false);
|
|
Assert.False(view.IffOverride(member, handle));
|
|
Assert.Equal(member.BelievesTrespassing(subject), view.BelievesTrespassing(member, handle));
|
|
}
|
|
|
|
[Fact]
|
|
public void SeeingIsTheMembersOwnEnemyList()
|
|
{
|
|
var faction = new Faction { Name = "Plain" };
|
|
var a = Pilot(faction, -10);
|
|
var b = Pilot(faction, 10);
|
|
var x = Subject();
|
|
See(b, x);
|
|
var view = ViewOf(a);
|
|
|
|
Assert.True(view.Sees(b, PilotView.Handle(x)));
|
|
Assert.False(view.Sees(a, PilotView.Handle(x)));
|
|
}
|
|
|
|
[Fact]
|
|
public void ATargetIsThePendingDesignationElseTheAppliedTarget()
|
|
{
|
|
var member = Pilot(null);
|
|
var x = Subject(100);
|
|
var y = Subject(200);
|
|
See(member, x);
|
|
See(member, y);
|
|
var view = ViewOf(member);
|
|
|
|
Assert.Null(view.TargetOf(member));
|
|
Assert.Null(view.AppliedTargetOf(member));
|
|
|
|
view.Designate(member, PilotView.Handle(x));
|
|
Assert.Equal(PilotView.Handle(x), view.TargetOf(member));
|
|
Assert.Null(view.AppliedTargetOf(member));
|
|
|
|
PilotApplier.Apply(member);
|
|
Assert.Equal(PilotView.Handle(x), view.AppliedTargetOf(member));
|
|
Assert.Equal(PilotView.Handle(x), view.TargetOf(member));
|
|
|
|
view.Designate(member, PilotView.Handle(y));
|
|
Assert.Equal(PilotView.Handle(y), view.TargetOf(member));
|
|
Assert.Equal(PilotView.Handle(x), view.AppliedTargetOf(member));
|
|
}
|
|
|
|
[Fact]
|
|
public void DesignatingTheAppliedTargetAgainWritesNothing()
|
|
{
|
|
var member = Pilot(null);
|
|
var x = Subject(100);
|
|
See(member, x);
|
|
var view = ViewOf(member);
|
|
view.Designate(member, PilotView.Handle(x));
|
|
PilotApplier.Apply(member);
|
|
|
|
view.Designate(member, PilotView.Handle(x));
|
|
Assert.Null(member.Command.Designated);
|
|
|
|
view.Designate(member, null);
|
|
Assert.True(member.Command.Designated.Value.IsNone);
|
|
PilotApplier.Apply(member);
|
|
view.Designate(member, null);
|
|
Assert.Null(member.Command.Designated);
|
|
}
|
|
|
|
[Fact]
|
|
public void AHandleIsTheContactAcrossItsAbsence()
|
|
{
|
|
var member = Pilot(null);
|
|
var x = Subject(100);
|
|
var y = Subject(200);
|
|
var view = ViewOf(member);
|
|
See(member, x);
|
|
var before = view.Picture().Single();
|
|
|
|
member.VisibleEnemies.Remove(x);
|
|
member.VisibleEntities.Remove(x);
|
|
Assert.Empty(view.Picture());
|
|
See(member, x);
|
|
|
|
Assert.Equal(before, view.Picture().Single());
|
|
Assert.Equal(PilotView.Handle(x), before);
|
|
Assert.True(before == PilotView.Handle(x));
|
|
Assert.Equal(PilotView.Handle(x).GetHashCode(), before.GetHashCode());
|
|
Assert.NotEqual(PilotView.Handle(y), before);
|
|
Assert.True(PilotView.Handle(y) != before);
|
|
}
|
|
|
|
[Fact]
|
|
public void ACombatingPilotFliesOnlyOnATrackedTarget()
|
|
{
|
|
var member = Pilot(null);
|
|
var subject = Subject(500, 300);
|
|
member.SetTarget(subject);
|
|
member.Position = new float3(500, 0, 0);
|
|
var agent = new Minion(member, FactionDoctrine.Default(_items.GameplaySettings).Combatant)
|
|
{ Task = new PatrolOrbitsTask { Circuit = new CultRecordKey[1] } };
|
|
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
agent.Update(.1f);
|
|
PilotApplier.Apply(member);
|
|
}
|
|
Assert.True((member.Command.Aim?.z ?? 0) < 100, "an untracked target is not flown on");
|
|
|
|
See(member, subject);
|
|
member.SetTarget(subject);
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
agent.Update(.1f);
|
|
PilotApplier.Apply(member);
|
|
}
|
|
Assert.Equal(300f, member.Command.Aim.Value.z, 1);
|
|
}
|
|
|
|
// The hostile part of Sees is the member's own: a member holding a sidemate's enemy as friendly (a hail it answered, an
|
|
// IFF override) does not see it as an enemy, though it tracks it. Kills: Sees reading VisibleEntities.
|
|
[Fact]
|
|
public void AMemberThatHoldsASidematesEnemyAsFriendlyDoesNotSeeItAsOne()
|
|
{
|
|
var faction = new Faction { Name = "Plain" };
|
|
var a = Pilot(faction, -10);
|
|
var b = Pilot(faction, 10);
|
|
var x = Subject();
|
|
See(b, x);
|
|
a.EntityInfoGathered[x] = 1;
|
|
a.VisibleEntities.Add(x);
|
|
a.SetIff(x, false);
|
|
var view = ViewOf(a);
|
|
var handle = PilotView.Handle(x);
|
|
|
|
Assert.NotNull(view.TrackOf(a, handle));
|
|
Assert.True(view.Sees(b, handle));
|
|
Assert.False(view.Sees(a, handle));
|
|
}
|
|
|
|
// A view answers for its members only: every per-member read of a ship the view does not hold is empty, however much
|
|
// that ship knows, and a write for it changes nothing. One assertion per read, each killing its dropped membership check.
|
|
[Fact]
|
|
public void AViewAnswersNothingForAShipThatIsNotItsMember()
|
|
{
|
|
var a = Pilot(null, -200);
|
|
var outsider = Pilot(null, 200);
|
|
var x = Subject();
|
|
See(outsider, x);
|
|
outsider.SetIff(x, true);
|
|
x.PresencePermitted = Observable.Return(false).ToReadOnlyReactiveProperty();
|
|
outsider.Command.Designate(new TargetRef(x));
|
|
var handle = PilotView.Handle(x);
|
|
var view = ViewOf(a);
|
|
Assert.NotNull(outsider.IffOverride(x));
|
|
Assert.True(outsider.BelievesTrespassing(x));
|
|
Assert.True(outsider.VisibleEnemies.Contains(x));
|
|
|
|
Assert.Null(view.TrackOf(outsider, handle));
|
|
Assert.False(view.Sees(outsider, handle));
|
|
Assert.Null(view.IffOverride(outsider, handle));
|
|
Assert.Null(view.Info(outsider, handle));
|
|
Assert.False(view.BelievesTrespassing(outsider, handle));
|
|
Assert.Null(view.TargetOf(outsider));
|
|
PilotApplier.Apply(outsider);
|
|
Assert.Null(view.AppliedTargetOf(outsider));
|
|
|
|
view.Designate(outsider, null);
|
|
Assert.Null(outsider.Command.Designated);
|
|
}
|
|
}
|