190 lines
6.7 KiB
C++
190 lines
6.7 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#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<StatCounter> 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<std::pair<StatKind, int>, 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<std::string> 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<std::string> objectiveNames() const;
|
|
|
|
StatSheet& stats(const std::string& player);
|
|
const StatSheet* statsFor(const std::string& player) const;
|
|
std::vector<std::string> 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<ScoreEntry> 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<ScoreEntry> 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<std::string> 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<std::string, ScoreObjective> objectives;
|
|
std::map<std::string, std::map<std::string, int>> manualScores;
|
|
std::map<std::string, StatSheet> sheets;
|
|
std::map<std::string, ScoreTeam> teams;
|
|
std::map<std::string, std::string> teamByPlayer;
|
|
std::array<std::string, static_cast<std::size_t>(DisplaySlot::Count)> slots;
|
|
};
|
|
|
|
} // namespace vf
|