17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 00:29:19 +02:00
Files
maszyna/tests/unit/test_handover.cpp
jerrrrycho c7f9796387 fix(ai): release horn when human driver takes over from AI (#210)
* fix(ai): release what the AI was holding when the player takes over

* fix(ai): keep the light selector lookup within its table, Sonar findings of the handover

- the light selector size comes from the vehicle definition (LightsList Size)
  and isn't limited, while the table holds 18 positions; a bigger selector
  read past the table, and so did a table index other than the front or
  rear one. Only the positions the table holds are looked at now, and the
  selector stays where it is for a bad table index (cpp:S2259),
- the lambdas of the handover capture what they use explicitly (cpp:S3608),
- the chained assignments resetting the headlight state on the AI taking
  over are split (cpp:S1121).
2026-10-16 14:44:30 +02:00

170 lines
5.6 KiB
C++

#include "stdafx.h"
#include "doctest.h"
#include "vehicle.h"
#include "vehicle/handover.h"
using test::definition;
using test::vehicle;
// what the human driver gets when taking a vehicle over from the AI (Q key). the AI operates some controls
// directly and only the AI ever released them, so the player inherited e.g. a horn blowing forever.
namespace {
int const pc1{light::headlight_left | light::headlight_upper | light::headlight_right};
int const allheadlights{pc1 | light::redmarker_left | light::redmarker_right};
vehicle cab_vehicle() {
vehicle cab{definition::load("cab_vehicle")};
cab->DirActive = 1; // the AI drives: reverser set
return cab;
}
} // namespace
TEST_CASE("handover: the horn the AI was sounding goes quiet (eu07/maszyna#511)") {
auto cab{cab_vehicle()};
SUBCASE("low tone") { cab->WarningSignal = 1; }
SUBCASE("high tone") { cab->WarningSignal = 2; }
SUBCASE("both tones") { cab->WarningSignal = 1 | 2; }
REQUIRE(cab->WarningSignal != 0);
handover::release_ai_controls(cab.mover(), cab.mover());
CHECK(cab->WarningSignal == 0);
}
TEST_CASE("handover: sanding the AI started stops") {
auto cab{cab_vehicle()};
cab->Sandbox(true); // the way the AI sands, bypassing the sanding button
REQUIRE(cab->SandDose);
handover::release_ai_controls(cab.mover(), cab.mover());
CHECK_FALSE(cab->SandDose);
}
TEST_CASE("handover: sanding the player or the automatic system asked for goes on") {
auto cab{cab_vehicle()};
SUBCASE("sanding button held") { cab->SandboxManual(true); }
SUBCASE("automatic sanding") {
cab->SandboxAutoAllow(true);
cab->SandboxAuto(true);
}
REQUIRE(cab->SandDose);
handover::release_ai_controls(cab.mover(), cab.mover());
CHECK(cab->SandDose);
}
TEST_CASE("handover: the departure signal the AI was giving stops") {
auto cab{cab_vehicle()};
cab->signal_departure(true);
REQUIRE(cab->DepartureSignal);
handover::release_ai_controls(cab.mover(), cab.mover());
CHECK_FALSE(cab->DepartureSignal);
}
TEST_CASE("handover: the brake releaser the AI was holding lets go") {
auto cab{cab_vehicle()};
cab->BrakeReleaser(1);
REQUIRE(cab->Hamulec->Releaser());
handover::release_ai_controls(cab.mover(), cab.mover());
CHECK_FALSE(cab->Hamulec->Releaser());
}
TEST_CASE("handover: nothing the AI didn't latch changes") {
auto cab{cab_vehicle()};
handover::release_ai_controls(cab.mover(), cab.mover());
CHECK(cab->WarningSignal == 0);
CHECK_FALSE(cab->SandDose);
CHECK_FALSE(cab->DepartureSignal);
CHECK_FALSE(cab->Hamulec->Releaser());
}
TEST_CASE("handover: the light selector moves to the position giving the head lights the AI has set") {
auto cab{cab_vehicle()};
REQUIRE(cab->LightsPos == 1); // untouched selector: lights off
SUBCASE("Pc1") {
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1, allheadlights) == 2);
}
SUBCASE("lights off") {
CHECK(handover::light_selector_position(cab.mover(), end::front, 0, allheadlights) == 1);
}
SUBCASE("vehicle without the upper headlight, which the AI's Pc1 can't light") {
auto const inventory{allheadlights & ~light::headlight_upper};
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1 & inventory, inventory) == 2);
}
SUBCASE("no position matches: the selector stays") {
CHECK(handover::light_selector_position(cab.mover(), end::front, light::headlight_left, allheadlights) == 1);
}
}
TEST_CASE("handover: the light selector stays within its table") {
auto cab{cab_vehicle()};
SUBCASE("selector bigger than the table, as a vehicle definition can declare") {
// only the positions the table holds are looked at, the rest can't match
for (auto &table : cab->Lights) {
std::fill(std::begin(table) + cab->LightsPosNo, std::end(table), 0);
}
cab->LightsPosNo = 100;
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1, allheadlights) == 2);
CHECK(handover::light_selector_position(cab.mover(), end::front, light::headlight_left, allheadlights) == 1);
}
SUBCASE("table other than the front or rear one: the selector stays") {
CHECK(handover::light_selector_position(cab.mover(), -1, pc1, allheadlights) == 1);
CHECK(handover::light_selector_position(cab.mover(), 2, pc1, allheadlights) == 1);
}
}
TEST_CASE("handover: the light selector already matching the lights stays") {
auto cab{cab_vehicle()};
cab->LightsPos = 2; // Pc1
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1, allheadlights) == 2);
}
TEST_CASE("handover: the headlight dimmer leaves the off position when the head lights are lit") {
auto cab{cab_vehicle()};
REQUIRE(cab->enableModernDimmer);
REQUIRE(cab->dimPositions.size() == 5);
cab->modernDimmerPosition = 2; // off, left there by the player before the AI took over
SUBCASE("to the default position") {
CHECK(handover::dimmer_position(cab.mover(), true) == 3);
}
SUBCASE("to the first regular position, when the default one is off as well") {
cab->modernDimmerDefaultPosition = 2;
CHECK(handover::dimmer_position(cab.mover(), true) == 3);
}
SUBCASE("unless the head lights are off anyway") {
CHECK(handover::dimmer_position(cab.mover(), false) == 2);
}
}
TEST_CASE("handover: the headlight dimmer in any other position stays") {
auto cab{cab_vehicle()};
for (auto const position : {0, 1, 3, 4}) {
CAPTURE(position);
cab->modernDimmerPosition = position;
CHECK(handover::dimmer_position(cab.mover(), true) == position);
}
}
TEST_CASE("handover: a vehicle without the modern dimmer keeps its dimmer state") {
vehicle wagon{definition::load("wagon")};
REQUIRE_FALSE(wagon->enableModernDimmer);
CHECK(handover::dimmer_position(wagon.mover(), true) == wagon->modernDimmerPosition);
}