diff --git a/tests/testScoreboard.cpp b/tests/testScoreboard.cpp index c1e8bcb..8a1e685 100644 --- a/tests/testScoreboard.cpp +++ b/tests/testScoreboard.cpp @@ -155,3 +155,219 @@ VF_TEST(manualObjectivesOnlyMoveWhenSomethingSetsThem) { CHECK(!board.tracked("bounty", "newcomer")); CHECK(board.statsFor("newcomer") == nullptr); + board.setScore("bounty", "a", 2147483647); + CHECK(board.addScore("bounty", "a", 10)); + CHECK_EQ(board.score("bounty", "a"), 2147483647); +} + +VF_TEST(entriesComeBackSortedAndFormattedForTheirObjective) { + Scoreboard board; + ScoreObjective bounty = manualObjective("bounty"); + board.addObjective(bounty); + board.setScore("bounty", "cara", 5); + board.setScore("bounty", "bea", 9); + board.setScore("bounty", "abel", 5); + + std::vector ranked = board.entries("bounty"); + CHECK_EQ(ranked.size(), static_cast(3)); + CHECK_EQ(ranked[0].player, std::string("bea")); + CHECK_EQ(ranked[1].player, std::string("abel")); + CHECK_EQ(ranked[2].player, std::string("cara")); + CHECK_EQ(ranked[0].formatted, std::string("9")); + + ScoreObjective ascending = manualObjective("race"); + ascending.sort = ScoreObjective::Sort::Ascending; + ascending.format = ScoreObjective::Format::Duration; + board.addObjective(ascending); + board.setScore("race", "bea", 20 * 65); + board.setScore("race", "abel", 20 * 3661); + board.setScore("race", "cara", 20 * 9); + std::vector fastest = board.entries("race"); + CHECK_EQ(fastest[0].player, std::string("cara")); + CHECK_EQ(fastest[0].formatted, std::string("9s")); + CHECK_EQ(fastest[1].formatted, std::string("1m 05s")); + CHECK_EQ(fastest[2].formatted, std::string("1h 01m 01s")); + + ScoreObjective walked = statObjective("walked", StatKind::DistanceWalked); + walked.format = ScoreObjective::Format::Distance; + board.addObjective(walked); + board.stats("abel").add(StatKind::DistanceWalked, 123456); + board.stats("bea").add(StatKind::DistanceWalked, 7); + std::vector distances = board.entries("walked"); + CHECK_EQ(distances.size(), static_cast(2)); + CHECK_EQ(distances[0].player, std::string("abel")); + CHECK_EQ(distances[0].formatted, std::string("1234.56 m")); + CHECK_EQ(distances[1].formatted, std::string("0.07 m")); + + CHECK(board.entries("missing").empty()); + CHECK_EQ(walked.formatScore(-250), std::string("-2.50 m")); + CHECK_EQ(ascending.formatScore(0), std::string("0s")); +} + +VF_TEST(displaySlotsShowOneObjectiveEachAndCanBeCleared) { + Scoreboard board; + board.addObjective(manualObjective("bounty")); + board.addObjective(statObjective("deaths", StatKind::Deaths)); + board.setScore("bounty", "abel", 3); + board.setScore("bounty", "bea", 8); + board.setScore("bounty", "cara", 1); + + CHECK(board.displayedObjective(DisplaySlot::Sidebar).empty()); + board.setDisplay(DisplaySlot::Sidebar, "bounty"); + board.setDisplay(DisplaySlot::PlayerList, "deaths"); + board.setDisplay(DisplaySlot::BelowName, "nothing at all"); + CHECK_EQ(board.displayedObjective(DisplaySlot::Sidebar), std::string("bounty")); + CHECK_EQ(board.displayedObjective(DisplaySlot::PlayerList), std::string("deaths")); + CHECK(board.displayedObjective(DisplaySlot::BelowName).empty()); + + std::vector sidebar = board.displayLines(DisplaySlot::Sidebar, 2); + CHECK_EQ(sidebar.size(), static_cast(2)); + CHECK_EQ(sidebar[0].player, std::string("bea")); + CHECK_EQ(sidebar[1].player, std::string("abel")); + CHECK_EQ(board.displayLines(DisplaySlot::Sidebar, 15).size(), static_cast(3)); + CHECK(board.displayLines(DisplaySlot::BelowName, 15).empty()); + + board.clearDisplay(DisplaySlot::Sidebar); + CHECK(board.displayedObjective(DisplaySlot::Sidebar).empty()); + CHECK(board.displayLines(DisplaySlot::Sidebar, 15).empty()); + + board.setDisplay(DisplaySlot::Sidebar, "bounty"); + CHECK(board.removeObjective("bounty")); + CHECK(!board.removeObjective("bounty")); + CHECK(board.displayedObjective(DisplaySlot::Sidebar).empty()); + CHECK_EQ(board.score("bounty", "bea"), 0); + + CHECK_EQ(std::string(displaySlotName(DisplaySlot::Sidebar)), std::string("sidebar")); + CHECK_EQ(std::string(displaySlotName(DisplaySlot::BelowName)), std::string("belowName")); +} + +VF_TEST(teamsDecideFriendlyFireNameVisibilityAndDisplayName) { + Scoreboard board; + ScoreTeam red = makeTeam("red", ScoreTeam::Color::Red); + red.friendlyFire = false; + red.prefix = "[R] "; + red.suffix = " *"; + red.members.push_back("abel"); + red.members.push_back("bea"); + CHECK(board.addTeam(red)); + CHECK(!board.addTeam(red)); + CHECK(board.addTeam(makeTeam("blue", ScoreTeam::Color::Blue))); + CHECK(board.joinTeam("blue", "cara")); + CHECK(!board.joinTeam("green", "cara")); + + CHECK_EQ(board.teamNames().size(), static_cast(2)); + CHECK(board.teamOf("abel") != nullptr); + CHECK_EQ(board.teamOf("abel")->name, std::string("red")); + CHECK_EQ(board.team("red")->members.size(), static_cast(2)); + CHECK(board.teamOf("nobody") == nullptr); + + CHECK(!board.friendlyFireAllowed("abel", "bea")); + CHECK(board.friendlyFireAllowed("abel", "cara")); + CHECK(board.friendlyFireAllowed("cara", "abel")); + CHECK(board.friendlyFireAllowed("nobody", "abel")); + + CHECK_EQ(board.displayNameOf("abel"), std::string("[R] abel *")); + CHECK_EQ(board.displayNameOf("cara"), std::string("cara")); + + // Moving a player leaves the old roster behind. + CHECK(board.joinTeam("blue", "bea")); + CHECK_EQ(board.team("red")->members.size(), static_cast(1)); + CHECK_EQ(board.teamOf("bea")->name, std::string("blue")); + CHECK(board.friendlyFireAllowed("abel", "bea")); + + CHECK(board.leaveTeam("bea")); + CHECK(!board.leaveTeam("bea")); + CHECK(board.teamOf("bea") == nullptr); + CHECK_EQ(board.team("blue")->members.size(), static_cast(1)); + + CHECK(board.removeTeam("blue")); + CHECK(!board.removeTeam("blue")); + CHECK(board.teamOf("cara") == nullptr); + CHECK_EQ(std::string(scoreTeamColorName(ScoreTeam::Color::Red)), std::string("red")); + CHECK_EQ(std::string(scoreTeamColorName(ScoreTeam::Color::Aqua)), std::string("aqua")); +} + +VF_TEST(nameVisibilityFollowsTheTeamRuleForTheWatcher) { + Scoreboard board; + ScoreTeam hidden = makeTeam("hidden", ScoreTeam::Color::Gray); + hidden.nameVisibility = ScoreTeam::Visibility::Never; + hidden.members.push_back("ghost"); + board.addTeam(hidden); + + ScoreTeam clan = makeTeam("clan", ScoreTeam::Color::Green); + clan.nameVisibility = ScoreTeam::Visibility::HideForOtherTeams; + clan.members.push_back("abel"); + clan.members.push_back("bea"); + board.addTeam(clan); + + ScoreTeam masked = makeTeam("masked", ScoreTeam::Color::Purple); + masked.nameVisibility = ScoreTeam::Visibility::HideForOwnTeam; + masked.members.push_back("cara"); + masked.members.push_back("dana"); + board.addTeam(masked); + + CHECK(!board.nameVisibleTo("abel", "ghost")); + CHECK(!board.nameVisibleTo("ghost", "ghost")); + + CHECK(board.nameVisibleTo("abel", "bea")); + CHECK(!board.nameVisibleTo("cara", "bea")); + + CHECK(!board.nameVisibleTo("dana", "cara")); + CHECK(board.nameVisibleTo("abel", "cara")); + + CHECK(board.nameVisibleTo("abel", "loner")); + CHECK(board.nameVisibleTo("loner", "loner")); +} + +VF_TEST(everyPlayerTouchedByTheBoardIsListedOnceInOrder) { + Scoreboard board; + board.addObjective(manualObjective("bounty")); + board.addObjective(statObjective("mined", StatKind::BlocksBroken)); + board.addTeam(makeTeam("red", ScoreTeam::Color::Red)); + + board.setScore("bounty", "cara", 1); + board.stats("abel").addBlockBroken(Block::Stone); + board.joinTeam("red", "bea"); + board.stats("cara").addBlockBroken(Block::Dirt, 4); + + std::vector everyone = board.players(); + CHECK_EQ(everyone.size(), static_cast(3)); + CHECK_EQ(everyone[0], std::string("abel")); + CHECK_EQ(everyone[1], std::string("bea")); + CHECK_EQ(everyone[2], std::string("cara")); + + // The automatic column only lists players who actually have a sheet. + std::vector mined = board.entries("mined"); + CHECK_EQ(mined.size(), static_cast(2)); + CHECK_EQ(mined[0].player, std::string("cara")); + CHECK_EQ(mined[0].score, 4); + CHECK_EQ(mined[1].player, std::string("abel")); +} + +VF_TEST(aStatisticObjectiveOnOneSubjectIgnoresTheOthers) { + Scoreboard board; + board.addObjective(statObjective("zombieHunter", StatKind::MobsKilled, + static_cast(Species::Zombie))); + board.addObjective(statObjective("anyKill", StatKind::MobsKilled)); + board.addObjective(statObjective("pickaxeUse", StatKind::ItemsUsed, + static_cast(ItemId::IronPickaxe))); + + StatSheet& sheet = board.stats("foksiu"); + sheet.addMobKilled(Species::Zombie, 3); + sheet.addMobKilled(Species::Sheep, 9); + sheet.addItemUsed(ItemId::IronPickaxe, 2); + sheet.addItemUsed(ItemId::WoodAxe, 40); + + CHECK_EQ(board.score("zombieHunter", "foksiu"), 3); + CHECK_EQ(board.score("anyKill", "foksiu"), 12); + CHECK_EQ(board.score("pickaxeUse", "foksiu"), 2); + + sheet.addMobKilled(Species::Zombie); + CHECK_EQ(board.score("zombieHunter", "foksiu"), 4); + CHECK_EQ(board.score("anyKill", "foksiu"), 13); + + // A stat far past the score range is clamped, not wrapped. + sheet.add(StatKind::PlayTicks, 4000000000ll); + board.addObjective(statObjective("played", StatKind::PlayTicks)); + CHECK_EQ(board.score("played", "foksiu"), 2147483647); +}