diff --git a/core/include/vf/Scoreboard.h b/core/include/vf/Scoreboard.h new file mode 100644 index 0000000..cb5da8f --- /dev/null +++ b/core/include/vf/Scoreboard.h @@ -0,0 +1,189 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Block.h" +#include "Effect.h" +#include "Item.h" +#include "Mob.h" + +namespace vf { + +// What a statistic counts. The three families differ in their unit: distances +// are whole centimetres, PlayTicks is world ticks, everything else is a plain +// tally. +enum class StatKind : uint8_t { + DistanceWalked, + DistanceSprinted, + DistanceSwum, + DistanceFallen, + DistanceClimbed, + BlocksBroken, + ItemsUsed, + MobsKilled, + Deaths, + DamageDealt, + DamageTaken, + PlayTicks, + Count +}; + +const char* statKindName(StatKind kind); +bool parseStatKindName(const std::string& text, StatKind& out); +// True for the kinds counted once per block, item or species. +bool statKindHasSubject(StatKind kind); +bool statKindIsDistance(StatKind kind); + +// One counted cell. `subject` is the block, item or species the tally belongs +// to, cast to int; it stays 0 for the kinds that count a single total. +struct StatCounter { + StatKind kind = StatKind::PlayTicks; + int subject = 0; + long long value = 0; +}; + +// Everything one player has done, as integers so a replay counts identically. +class StatSheet { +public: + void add(StatKind kind, long long amount); + void addFor(StatKind kind, int subject, long long amount); + void addBlockBroken(Block block, long long amount = 1); + void addItemUsed(ItemId item, long long amount = 1); + void addMobKilled(Species species, long long amount = 1); + void clear(); + + // Summed over every subject of that kind. + long long total(StatKind kind) const; + long long value(StatKind kind, int subject) const; + long long blocksBroken(Block block) const; + long long itemsUsed(ItemId item) const; + long long mobsKilled(Species species) const; + long long distanceTravelled() const; + + // Sorted by kind then subject, so two equal sheets serialize identically. + std::vector counters() const; + int distinctSubjects(StatKind kind) const; + // Incremented on every change, so a cache can tell it went stale. + long long revision() const { return changes; } + +private: + std::map, long long> cells; + long long changes = 0; +}; + +// Where a scoreboard objective is shown. +enum class DisplaySlot : uint8_t { PlayerList, Sidebar, BelowName, Count }; + +const char* displaySlotName(DisplaySlot slot); + +// A tracked score column. A Manual objective only moves when something calls +// setScore or addScore; a Statistic objective is a live view of the players' +// stat sheets and needs no update step at all. +struct ScoreObjective { + enum class Criterion : uint8_t { Manual, Statistic }; + enum class Sort : uint8_t { Descending, Ascending }; + enum class Format : uint8_t { Integer, Distance, Duration }; + + std::string name; + std::string displayName; + Criterion criterion = Criterion::Manual; + StatKind statKind = StatKind::PlayTicks; + // -1 totals every subject of the kind, otherwise it is the one block, item + // or species this objective follows. + int statSubject = -1; + Sort sort = Sort::Descending; + Format format = Format::Integer; + + bool automatic() const { return criterion == Criterion::Statistic; } + // The score rendered for display: a plain number, "12.34 m" or "1h 02m 03s". + std::string formatScore(int score) const; +}; + +struct ScoreEntry { + std::string player; + int score = 0; + std::string formatted; +}; + +// A named group of players with its own colour, friendly fire rule and name +// visibility. +struct ScoreTeam { + enum class Color : uint8_t { + White, Gray, Black, Red, Green, Blue, Yellow, Aqua, Purple, Gold, Count + }; + enum class Visibility : uint8_t { Always, Never, HideForOtherTeams, HideForOwnTeam }; + + std::string name; + std::string displayName; + std::string prefix; + std::string suffix; + Color color = Color::White; + bool friendlyFire = true; + Visibility nameVisibility = Visibility::Always; + std::vector members; + + bool hasMember(const std::string& player) const; +}; + +const char* scoreTeamColorName(ScoreTeam::Color color); + +// Objectives, scores, display slots, teams and the per player stat sheets the +// automatic objectives read. Every listing comes back sorted, so two runs of +// the same game produce the same board. +class Scoreboard { +public: + bool addObjective(const ScoreObjective& objective); + bool removeObjective(const std::string& name); + const ScoreObjective* objective(const std::string& name) const; + std::vector objectiveNames() const; + + StatSheet& stats(const std::string& player); + const StatSheet* statsFor(const std::string& player) const; + std::vector players() const; + + // Refused for automatic objectives: their value belongs to the stat sheet. + bool setScore(const std::string& objectiveName, const std::string& player, int value); + bool addScore(const std::string& objectiveName, const std::string& player, int delta); + bool resetScore(const std::string& objectiveName, const std::string& player); + void resetPlayer(const std::string& player); + + int score(const std::string& objectiveName, const std::string& player) const; + bool tracked(const std::string& objectiveName, const std::string& player) const; + + // Sorted by the objective's sort order, ties broken by player name. + std::vector entries(const std::string& objectiveName) const; + + void setDisplay(DisplaySlot slot, const std::string& objectiveName); + void clearDisplay(DisplaySlot slot); + const std::string& displayedObjective(DisplaySlot slot) const; + std::vector displayLines(DisplaySlot slot, int limit = 15) const; + + bool addTeam(const ScoreTeam& team); + bool removeTeam(const std::string& name); + const ScoreTeam* team(const std::string& name) const; + const ScoreTeam* teamOf(const std::string& player) const; + bool joinTeam(const std::string& teamName, const std::string& player); + bool leaveTeam(const std::string& player); + std::vector teamNames() const; + + bool friendlyFireAllowed(const std::string& attacker, const std::string& victim) const; + bool nameVisibleTo(const std::string& viewer, const std::string& target) const; + std::string displayNameOf(const std::string& player) const; + +private: + int statScore(const ScoreObjective& objective, const std::string& player) const; + + std::map objectives; + std::map> manualScores; + std::map sheets; + std::map teams; + std::map teamByPlayer; + std::array(DisplaySlot::Count)> slots; +}; + +} // namespace vf