This commit is contained in:
EmosewaMC 2023-05-30 04:11:37 -07:00
parent f26e66da4d
commit 0107d05d55
7 changed files with 123 additions and 175 deletions

View File

@ -11,12 +11,15 @@
#include "GeneralUtils.h"
#include "Entity.h"
#include "LDFFormat.h"
#include "DluAssert.h"
#include <sstream>
#include "CDActivitiesTable.h"
#include "Metrics.hpp"
LeaderboardManager::LeaderboardCache LeaderboardManager::leaderboardCache = {};
namespace LeaderboardManager {
LeaderboardCache leaderboardCache;
}
Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type leaderboardType) {
this->gameID = gameID;
@ -143,7 +146,7 @@ void Leaderboard::QueryToLdf(std::unique_ptr<sql::ResultSet>& rows) {
}
}
const std::string Leaderboard::GetColumns(Leaderboard::Type leaderboardType) {
const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType) {
std::string columns;
switch (leaderboardType) {
case Type::ShootingGallery:
@ -177,7 +180,7 @@ const std::string Leaderboard::GetColumns(Leaderboard::Type leaderboardType) {
return columns;
}
const std::string Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) {
const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) {
std::string columns;
switch (leaderboardType) {
case Type::ShootingGallery:
@ -211,7 +214,7 @@ const std::string Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType
return columns;
}
const std::string Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) {
const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) {
std::string orderBase;
switch (leaderboardType) {
case Type::ShootingGallery:
@ -296,19 +299,18 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) {
)
)QUERY";
const std::string orderBase = GetOrdering(this->leaderboardType);
const std::string selectBase = GetColumns(this->leaderboardType);
const auto orderBase = GetOrdering(this->leaderboardType);
const auto selectBase = GetColumns(this->leaderboardType);
constexpr uint16_t STRING_LENGTH = 1526;
char lookupBuffer[STRING_LENGTH];
// If we are getting the friends leaderboard, add the friends query, otherwise fill it in with nothing.
if (this->infoType == InfoType::Friends) {
snprintf(lookupBuffer, STRING_LENGTH, queryBase.c_str(),
orderBase.c_str(), friendsQuery, selectBase.c_str(), resultStart, resultEnd);
}
else {
snprintf(lookupBuffer, STRING_LENGTH, queryBase.c_str(),
orderBase.c_str(), "", selectBase.c_str(), resultStart, resultEnd);
snprintf(lookupBuffer, STRING_LENGTH, queryBase.data(),
orderBase.data(), friendsQuery, selectBase.data(), resultStart, resultEnd);
} else {
snprintf(lookupBuffer, STRING_LENGTH, queryBase.data(),
orderBase.data(), "", selectBase.data(), resultStart, resultEnd);
}
std::string baseLookupStr;
@ -355,186 +357,99 @@ void Leaderboard::Send(LWOOBJID targetID) {
}
}
void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t argumentCount, ...) {
va_list args;
va_start(args, argumentCount);
SaveScore(playerID, gameID, leaderboardType, args);
va_end(args);
}
std::string LeaderboardManager::FormatInsert(const Leaderboard::Type& type, const Score& score, const bool useUpdate) {
auto insertFormat = Leaderboard::GetInsertFormat(type);
std::string FormatInsert(const std::string& columns, const std::string& format, va_list args, bool useUpdate) {
const char* insertClause = "INSERT";
const char* updateClause = "UPDATE";
const char* queryType = useUpdate ? updateClause : insertClause;
auto* queryType = useUpdate ? "UPDATE" : "INSERT";
const char* insertFilter = ", character_id = ?, game_id = ?, timesPlayed = 1";
const char* updateFilter = ", timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?";
const char* usedFilter = useUpdate ? updateFilter : insertFilter;
auto* usedFilter = useUpdate ?
", timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?" :
", character_id = ?, game_id = ?";
// First fill in the format
constexpr uint16_t STRING_LENGTH = 400;
char formattedInsert[STRING_LENGTH];
auto queryBase = "%s leaderboard SET %s %s;";
snprintf(formattedInsert, STRING_LENGTH, queryBase, queryType, format.c_str(), usedFilter);
snprintf(formattedInsert, STRING_LENGTH, "%s leaderboard SET %s %s;", queryType, insertFormat.data(), usedFilter);
// Then fill in our score
char finishedQuery[STRING_LENGTH];
vsnprintf(finishedQuery, STRING_LENGTH, formattedInsert, args);
snprintf(finishedQuery, STRING_LENGTH, formattedInsert, score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore());
DluAssert()
return finishedQuery;
}
void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list activityScore) {
std::string selectedColumns = Leaderboard::GetColumns(leaderboardType);
std::string insertFormat = Leaderboard::GetInsertFormat(leaderboardType);
const char* lookup = "SELECT %s FROM leaderboard WHERE character_id = ? AND game_id = ?;";
void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t primaryScore, uint32_t secondaryScore, uint32_t tertiaryScore) {
auto* lookup = "SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;";
constexpr uint16_t STRING_LENGTH = 400;
char lookupBuffer[STRING_LENGTH];
snprintf(lookupBuffer, STRING_LENGTH, lookup, selectedColumns.c_str());
std::unique_ptr<sql::PreparedStatement> query(Database::CreatePreppedStmt(lookupBuffer));
std::unique_ptr<sql::PreparedStatement> query(Database::CreatePreppedStmt(lookup));
query->setInt(1, playerID);
query->setInt(2, gameID);
std::unique_ptr<sql::ResultSet> myScoreResult(query->executeQuery());
std::va_list argsCopy;
va_copy(argsCopy, activityScore);
std::string saveQuery;
Score newScore(primaryScore, secondaryScore, tertiaryScore);
if (myScoreResult->next()) {
Score oldScore;
bool lowerScoreBetter = false;
switch (leaderboardType) {
// Higher score better
case Leaderboard::Type::ShootingGallery: {
int32_t oldScore = myScoreResult->getInt("score");
int32_t score;
score = va_arg(argsCopy, int32_t);
int32_t oldHitPercentage = myScoreResult->getFloat("hitPercentage");
int32_t hitPercentage;
hitPercentage = va_arg(argsCopy, int32_t);
int32_t oldStreak = myScoreResult->getInt("streak");
int32_t streak;
streak = va_arg(argsCopy, int32_t);
if (score > oldScore || // If score is better
(score == oldScore && hitPercentage > oldHitPercentage) || // or if the score is tied and the hitPercentage is better
(score == oldScore && hitPercentage == oldHitPercentage && streak > oldStreak)) { // or if the score and hitPercentage are tied and the streak is better
saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true);
}
break;
}
case Leaderboard::Type::Racing: {
uint32_t oldLapTime = myScoreResult->getFloat("bestLapTime");
uint32_t lapTime;
lapTime = va_arg(argsCopy, uint32_t);
uint32_t oldTime = myScoreResult->getFloat("bestTime");
uint32_t newTime;
newTime = va_arg(argsCopy, uint32_t);
bool won;
won = va_arg(argsCopy, int32_t);
if (newTime < oldTime ||
(newTime == oldTime && lapTime < oldLapTime)) {
saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true);
} else if (won) {
std::unique_ptr<sql::PreparedStatement> incrementStatement(Database::CreatePreppedStmt("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;"));
incrementStatement->setInt(1, playerID);
incrementStatement->setInt(2, gameID);
incrementStatement->executeUpdate();
}
break;
}
case Leaderboard::Type::UnusedLeaderboard4: {
int32_t oldScore = myScoreResult->getInt("score");
int32_t points;
points = va_arg(argsCopy, int32_t);
if (points > oldScore) {
saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true);
}
break;
}
case Leaderboard::Type::MonumentRace: {
int32_t oldTime = myScoreResult->getInt("bestTime");
int32_t time;
time = va_arg(argsCopy, int32_t);
if (time < oldTime) {
saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true);
}
oldScore.SetPrimaryScore(myScoreResult->getInt("score"));
oldScore.SetSecondaryScore(myScoreResult->getInt("hitPercentage"));
oldScore.SetTertiaryScore(myScoreResult->getInt("streak"));
break;
}
case Leaderboard::Type::FootRace: {
int32_t oldTime = myScoreResult->getInt("bestTime");
int32_t time;
time = va_arg(argsCopy, int32_t);
if (time > oldTime) {
saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true);
}
oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime"));
break;
}
case Leaderboard::Type::Survival: {
int32_t oldTime = myScoreResult->getInt("bestTime");
int32_t time;
time = va_arg(argsCopy, int32_t);
int32_t oldPoints = myScoreResult->getInt("score");
int32_t points;
points = va_arg(argsCopy, int32_t);
if (points > oldPoints || (points == oldPoints && time < oldTime)) {
saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true);
}
// Config option may reverse these
oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime"));
oldScore.SetSecondaryScore(myScoreResult->getInt("score"));
break;
}
case Leaderboard::Type::SurvivalNS: {
int32_t oldTime = myScoreResult->getInt("bestTime");
int32_t time;
time = va_arg(argsCopy, int32_t);
int32_t oldWave = myScoreResult->getInt("score");
int32_t wave;
wave = va_arg(argsCopy, int32_t);
if (time < oldTime || (time == oldTime && wave > oldWave)) {
saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true);
}
oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime"));
oldScore.SetSecondaryScore(myScoreResult->getInt("score"));
break;
}
case Leaderboard::Type::UnusedLeaderboard4:
case Leaderboard::Type::Donations: {
int32_t oldScore = myScoreResult->getInt("score");
int32_t score;
score = va_arg(argsCopy, int32_t);
if (score > oldScore) {
saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true);
}
oldScore.SetPrimaryScore(myScoreResult->getInt("score"));
break;
}
case Leaderboard::Type::Racing:
oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime"));
oldScore.SetSecondaryScore(myScoreResult->getInt("bestLapTime"));
lowerScoreBetter = true;
break;
case Leaderboard::Type::MonumentRace:
oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime"));
lowerScoreBetter = true;
// Do score checking here
break;
case Leaderboard::Type::None:
default:
Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType);
va_end(argsCopy);
Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType);
return;
}
bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore;
if (newHighScore) {
saveQuery = FormatInsert(leaderboardType, newScore, true);
} else if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore) {
saveQuery = "UPDATE leaderboard SET numWins = numWins + 1, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;";
} else {
saveQuery = "UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;";
}
} else {
saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, false);
}
std::unique_ptr<sql::PreparedStatement> saveStatement;
if (!saveQuery.empty()) {
Game::logger->Log("LeaderboardManager", "Executing update with query %s", saveQuery.c_str());
std::unique_ptr<sql::PreparedStatement> updateStatement(Database::CreatePreppedStmt(saveQuery));
saveStatement = std::move(updateStatement);
} else {
Game::logger->Log("LeaderboardManager", "No new score to save, incrementing numTimesPlayed");
// Increment the numTimes this player has played this game.
std::unique_ptr<sql::PreparedStatement> updateStatement(Database::CreatePreppedStmt("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;"));
saveStatement = std::move(updateStatement);
saveQuery = FormatInsert(leaderboardType, newScore, false);
}
std::unique_ptr<sql::PreparedStatement> saveStatement(Database::CreatePreppedStmt(saveQuery));
saveStatement->setInt(1, playerID);
saveStatement->setInt(2, gameID);
saveStatement->execute();
va_end(argsCopy);
}
void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart, uint32_t resultEnd) {

View File

@ -1,7 +1,8 @@
#pragma once
#include <climits>
#ifndef __LEADERBOARDMANAGER__H__
#define __LEADERBOARDMANAGER__H__
#include <map>
#include <memory>
#include <string_view>
#include <vector>
#include "Singleton.h"
@ -62,7 +63,7 @@ public:
/**
* Builds the leaderboard from the database based on the associated gameID
*
*
* @param resultStart The index to start the leaderboard at. Zero indexed.
* @param resultEnd The index to end the leaderboard at. Zero indexed.
*/
@ -74,9 +75,9 @@ public:
void Send(LWOOBJID targetID);
// Helper functions to get the columns, ordering and insert format for a leaderboard
static const std::string GetColumns(Type leaderboardType);
static const std::string GetInsertFormat(Type leaderboardType);
static const std::string GetOrdering(Type leaderboardType);
static const std::string_view GetColumns(Type leaderboardType);
static const std::string_view GetInsertFormat(Type leaderboardType);
static const std::string_view GetOrdering(Type leaderboardType);
private:
inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data);
@ -98,9 +99,40 @@ private:
bool weekly;
};
class LeaderboardManager : public Singleton<LeaderboardManager> {
namespace LeaderboardManager {
class Score {
public:
Score() {
primaryScore = 0;
secondaryScore = 0;
tertiaryScore = 0;
}
Score(uint32_t primaryScore, uint32_t secondaryScore = 0, uint32_t tertiaryScore = 0) {
this->primaryScore = primaryScore;
this->secondaryScore = secondaryScore;
this->tertiaryScore = tertiaryScore;
}
bool operator<(const Score& rhs) const {
return primaryScore < rhs.primaryScore || (primaryScore == rhs.primaryScore && secondaryScore < rhs.secondaryScore) || (primaryScore == rhs.primaryScore && secondaryScore == rhs.secondaryScore && tertiaryScore < rhs.tertiaryScore);
}
bool operator>(const Score& rhs) const {
return primaryScore > rhs.primaryScore || (primaryScore == rhs.primaryScore && secondaryScore > rhs.secondaryScore) || (primaryScore == rhs.primaryScore && secondaryScore == rhs.secondaryScore && tertiaryScore > rhs.tertiaryScore);
}
void SetPrimaryScore(const uint32_t score) { primaryScore = score; }
uint32_t GetPrimaryScore() const { return primaryScore; }
void SetSecondaryScore(const uint32_t score) { secondaryScore = score; }
uint32_t GetSecondaryScore() const { return secondaryScore; }
void SetTertiaryScore(const uint32_t score) { tertiaryScore = score; }
uint32_t GetTertiaryScore() const { return tertiaryScore; }
private:
uint32_t primaryScore;
uint32_t secondaryScore;
uint32_t tertiaryScore;
};
using LeaderboardCache = std::map<GameID, Leaderboard::Type>;
public:
void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart = 0, uint32_t resultEnd = 10);
/**
@ -111,12 +143,13 @@ public:
* @param argumentCount The number of arguments in the va_list
* @param ... The score to save
*/
void SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t argumentCount, ...);
void SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t primaryScore, uint32_t secondaryScore = 0, uint32_t tertiaryScore = 0);
static Leaderboard::Type GetLeaderboardType(const GameID gameID);
static LeaderboardCache leaderboardCache;
private:
void SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list args);
void GetLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID = LWOOBJID_EMPTY);
std::string FormatInsert(const Leaderboard::Type& type, const Score& score, const bool useUpdate);
Leaderboard::Type GetLeaderboardType(const GameID gameID);
extern LeaderboardCache leaderboardCache;
};
#endif //!__LEADERBOARDMANAGER__H__

View File

@ -400,8 +400,8 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player,
const auto score = m_LoadedPlayers * 10 + data->finished;
LootGenerator::Instance().GiveActivityLoot(player, m_Parent, m_ActivityID, score);
auto leaderboardType = LeaderboardManager::Instance().GetLeaderboardType(m_ActivityID);
LeaderboardManager::Instance().SaveScore(player->GetObjectID(), m_ActivityID, leaderboardType, 3, data->bestLapTime, data->raceTime, data->finished == 1);
// auto leaderboardType = LeaderboardManager::Instance().GetLeaderboardType(m_ActivityID);
// LeaderboardManager::Instance().SaveScore(player->GetObjectID(), m_ActivityID, leaderboardType, 3, data->bestLapTime, data->raceTime, data->finished == 1);
// Giving rewards
GameMessages::SendNotifyRacingClient(

View File

@ -1661,7 +1661,7 @@ void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream
bool weekly = inStream->ReadBit();
LeaderboardManager::Instance().SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID(), resultsStart, resultsEnd);
// LeaderboardManager::Instance().SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID(), resultsStart, resultsEnd);
}
void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream* inStream, Entity* entity) {

View File

@ -94,7 +94,7 @@ void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std
EntityManager::Instance()->SerializeEntity(self);
auto leaderboardType = LeaderboardManager::GetLeaderboardType(scriptedActivityComponent->GetActivityID());
LeaderboardManager::Instance().SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), leaderboardType, 1, finish);
// LeaderboardManager::Instance().SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), leaderboardType, 1, finish);
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard",
scriptedActivityComponent->GetActivityID(), 0, sender->GetObjectID(),

View File

@ -88,7 +88,7 @@ void ActivityManager::SaveScore(Entity* self, LWOOBJID playerID, uint32_t val1,
// Save the new score to the leaderboard and show the leaderboard to the player
auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID);
Game::logger->Log("ActivityManager", "leaderboard type %i %i args %i %i %i", leaderboardType, gameID, val1, val2, val3);
LeaderboardManager::Instance().SaveScore(playerID, gameID, leaderboardType, 3, val1, val2, val3);
// LeaderboardManager::Instance().SaveScore(playerID, gameID, leaderboardType, 3, val1, val2, val3);
// Makes the leaderboard show up for the player
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", gameID, 0, playerID, "", player->GetSystemAddress());
@ -125,7 +125,7 @@ void ActivityManager::GetLeaderboardData(Entity* self, const LWOOBJID playerID,
// Save the new score to the leaderboard and show the leaderboard to the player
auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID);
Game::logger->Log("ActivityManager", "gameID %i", gameID, activityID);
LeaderboardManager::Instance().SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, self->GetObjectID(), 0, numResults);
// LeaderboardManager::Instance().SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, self->GetObjectID(), 0, numResults);
}
void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerName, const float_t updateInterval,

View File

@ -1,12 +1,12 @@
ALTER TABLE leaderboard
ADD COLUMN hitPercentage FLOAT NOT NULL DEFAULT 0,
ADD COLUMN hitPercentage INT NOT NULL DEFAULT 0,
ADD COLUMN streak INT NOT NULL DEFAULT 0,
ADD COLUMN bestLapTime FLOAT NOT NULL DEFAULT 0,
ADD COLUMN bestLapTime INT NOT NULL DEFAULT 0,
ADD COLUMN numWins INT NOT NULL DEFAULT 0,
ADD COLUMN timesPlayed INT NOT NULL DEFAULT 0,
MODIFY time FLOAT NOT NULL DEFAULT 0;
ADD COLUMN timesPlayed INT NOT NULL DEFAULT 1,
MODIFY time INT NOT NULL DEFAULT 0;
ALTER TABLE leaderboard CHANGE time bestTime float;
ALTER TABLE leaderboard CHANGE time bestTime INT;
ALTER TABLE leaderboard CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP();
UPDATE leaderboard SET streak = bestTime where game_id = 1864;