diff --git a/core/src/Scoreboard.cpp b/core/src/Scoreboard.cpp index 57af12f..cbc11ef 100644 --- a/core/src/Scoreboard.cpp +++ b/core/src/Scoreboard.cpp @@ -151,3 +151,176 @@ const char* displaySlotName(DisplaySlot slot) { return "unknown"; } +std::string ScoreObjective::formatScore(int score) const { + if (format == Format::Distance) { + long long centimetres = score; + std::string sign = centimetres < 0 ? "-" : ""; + long long magnitude = centimetres < 0 ? -centimetres : centimetres; + return sign + std::to_string(magnitude / 100) + "." + twoDigits(magnitude % 100) + " m"; + } + if (format == Format::Duration) { + long long ticks = score < 0 ? 0 : score; + long long seconds = ticks / ticksPerSecond; + long long hours = seconds / 3600; + long long minutes = (seconds % 3600) / 60; + long long rest = seconds % 60; + if (hours > 0) + return std::to_string(hours) + "h " + twoDigits(minutes) + "m " + twoDigits(rest) + "s"; + if (minutes > 0) return std::to_string(minutes) + "m " + twoDigits(rest) + "s"; + return std::to_string(rest) + "s"; + } + return std::to_string(score); +} + +bool ScoreTeam::hasMember(const std::string& player) const { + return std::find(members.begin(), members.end(), player) != members.end(); +} + +const char* scoreTeamColorName(ScoreTeam::Color color) { + switch (color) { + case ScoreTeam::Color::White: return "white"; + case ScoreTeam::Color::Gray: return "gray"; + case ScoreTeam::Color::Black: return "black"; + case ScoreTeam::Color::Red: return "red"; + case ScoreTeam::Color::Green: return "green"; + case ScoreTeam::Color::Blue: return "blue"; + case ScoreTeam::Color::Yellow: return "yellow"; + case ScoreTeam::Color::Aqua: return "aqua"; + case ScoreTeam::Color::Purple: return "purple"; + case ScoreTeam::Color::Gold: return "gold"; + case ScoreTeam::Color::Count: break; + } + return "unknown"; +} + +bool Scoreboard::addObjective(const ScoreObjective& objective) { + if (objective.name.empty()) return false; + if (objectives.count(objective.name) > 0u) return false; + objectives[objective.name] = objective; + return true; +} + +bool Scoreboard::removeObjective(const std::string& name) { + if (objectives.erase(name) == 0u) return false; + manualScores.erase(name); + for (std::string& slot : slots) + if (slot == name) slot.clear(); + return true; +} + +const ScoreObjective* Scoreboard::objective(const std::string& name) const { + auto found = objectives.find(name); + return found == objectives.end() ? nullptr : &found->second; +} + +std::vector Scoreboard::objectiveNames() const { + std::vector out; + out.reserve(objectives.size()); + for (const auto& entry : objectives) out.push_back(entry.first); + return out; +} + +StatSheet& Scoreboard::stats(const std::string& player) { return sheets[player]; } + +const StatSheet* Scoreboard::statsFor(const std::string& player) const { + auto found = sheets.find(player); + return found == sheets.end() ? nullptr : &found->second; +} + +std::vector Scoreboard::players() const { + std::vector out; + for (const auto& sheet : sheets) out.push_back(sheet.first); + for (const auto& objectiveScores : manualScores) + for (const auto& entry : objectiveScores.second) + if (std::find(out.begin(), out.end(), entry.first) == out.end()) + out.push_back(entry.first); + for (const auto& membership : teamByPlayer) + if (std::find(out.begin(), out.end(), membership.first) == out.end()) + out.push_back(membership.first); + std::sort(out.begin(), out.end()); + return out; +} + +bool Scoreboard::setScore(const std::string& objectiveName, const std::string& player, int value) { + const ScoreObjective* target = objective(objectiveName); + if (target == nullptr || target->automatic() || player.empty()) return false; + manualScores[objectiveName][player] = value; + return true; +} + +bool Scoreboard::addScore(const std::string& objectiveName, const std::string& player, int delta) { + const ScoreObjective* target = objective(objectiveName); + if (target == nullptr || target->automatic() || player.empty()) return false; + long long updated = static_cast(score(objectiveName, player)) + delta; + manualScores[objectiveName][player] = clampToScore(updated); + return true; +} + +bool Scoreboard::resetScore(const std::string& objectiveName, const std::string& player) { + auto found = manualScores.find(objectiveName); + if (found == manualScores.end()) return false; + return found->second.erase(player) > 0u; +} + +void Scoreboard::resetPlayer(const std::string& player) { + for (auto& objectiveScores : manualScores) objectiveScores.second.erase(player); + sheets.erase(player); +} + +int Scoreboard::statScore(const ScoreObjective& target, const std::string& player) const { + const StatSheet* sheet = statsFor(player); + if (sheet == nullptr) return 0; + if (target.statSubject < 0) return clampToScore(sheet->total(target.statKind)); + return clampToScore(sheet->value(target.statKind, target.statSubject)); +} + +int Scoreboard::score(const std::string& objectiveName, const std::string& player) const { + const ScoreObjective* target = objective(objectiveName); + if (target == nullptr) return 0; + if (target->automatic()) return statScore(*target, player); + auto objectiveScores = manualScores.find(objectiveName); + if (objectiveScores == manualScores.end()) return 0; + auto entry = objectiveScores->second.find(player); + return entry == objectiveScores->second.end() ? 0 : entry->second; +} + +bool Scoreboard::tracked(const std::string& objectiveName, const std::string& player) const { + const ScoreObjective* target = objective(objectiveName); + if (target == nullptr) return false; + if (target->automatic()) return sheets.count(player) > 0u; + auto objectiveScores = manualScores.find(objectiveName); + if (objectiveScores == manualScores.end()) return false; + return objectiveScores->second.count(player) > 0u; +} + +std::vector Scoreboard::entries(const std::string& objectiveName) const { + std::vector out; + const ScoreObjective* target = objective(objectiveName); + if (target == nullptr) return out; + if (target->automatic()) { + for (const auto& sheet : sheets) { + ScoreEntry entry; + entry.player = sheet.first; + entry.score = statScore(*target, sheet.first); + out.push_back(entry); + } + } else { + auto objectiveScores = manualScores.find(objectiveName); + if (objectiveScores != manualScores.end()) { + for (const auto& scored : objectiveScores->second) { + ScoreEntry entry; + entry.player = scored.first; + entry.score = scored.second; + out.push_back(entry); + } + } + } + const bool ascending = target->sort == ScoreObjective::Sort::Ascending; + std::sort(out.begin(), out.end(), [ascending](const ScoreEntry& a, const ScoreEntry& b) { + if (a.score != b.score) return ascending ? a.score < b.score : a.score > b.score; + return a.player < b.player; + }); + for (ScoreEntry& entry : out) entry.formatted = target->formatScore(entry.score); + return out; +} +