Merge branch 'main' into windows-clang

This commit is contained in:
Jett 2024-12-08 20:40:40 +00:00 committed by GitHub
commit 9d52c0fb84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 116323 additions and 41753 deletions

View File

@ -43,6 +43,7 @@ jobs:
build/*/*.ini build/*/*.ini
build/*/*.so build/*/*.so
build/*/*.dll build/*/*.dll
build/*/*.dylib
build/*/vanity/ build/*/vanity/
build/*/navmeshes/ build/*/navmeshes/
build/*/migrations/ build/*/migrations/

View File

@ -69,6 +69,7 @@ set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "" FORCE)
# Disabled no-register # Disabled no-register
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas. # Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
if(UNIX) if(UNIX)
add_link_options("-Wl,-rpath,$ORIGIN/")
add_compile_options("-fPIC") add_compile_options("-fPIC")
add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0) add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0)
@ -318,7 +319,7 @@ add_subdirectory(dPhysics)
add_subdirectory(dServer) add_subdirectory(dServer)
# Create a list of common libraries shared between all binaries # Create a list of common libraries shared between all binaries
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "MariaDB::ConnCpp" "magic_enum") set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "magic_enum")
# Add platform specific common libraries # Add platform specific common libraries
if(UNIX) if(UNIX)

View File

@ -92,4 +92,5 @@
"jobs": 2 "jobs": 2
} }
] ]
} }

View File

@ -60,7 +60,7 @@ int main(int argc, char** argv) {
try { try {
Database::Connect(); Database::Connect();
} catch (sql::SQLException& ex) { } catch (std::exception& ex) {
LOG("Got an error while connecting to the database: %s", ex.what()); LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("AuthServer"); Database::Destroy("AuthServer");
delete Game::server; delete Game::server;

View File

@ -81,7 +81,7 @@ int main(int argc, char** argv) {
//Connect to the MySQL Database //Connect to the MySQL Database
try { try {
Database::Connect(); Database::Connect();
} catch (sql::SQLException& ex) { } catch (std::exception& ex) {
LOG("Got an error while connecting to the database: %s", ex.what()); LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("ChatServer"); Database::Destroy("ChatServer");
delete Game::server; delete Game::server;

View File

@ -123,7 +123,7 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
Database::Get()->UpdateUgcModelData(model.id, outputStringStream); Database::Get()->UpdateUgcModelData(model.id, outputStringStream);
LOG("Updated model %i to sd0", model.id); LOG("Updated model %i to sd0", model.id);
updatedModels++; updatedModels++;
} catch (sql::SQLException exception) { } catch (std::exception& exception) {
LOG("Failed to update model %i. This model should be inspected manually to see why." LOG("Failed to update model %i. This model should be inspected manually to see why."
"The database error is %s", model.id, exception.what()); "The database error is %s", model.id, exception.what());
} }

View File

@ -2,6 +2,12 @@ add_subdirectory(CDClientDatabase)
add_subdirectory(GameDatabase) add_subdirectory(GameDatabase)
add_library(dDatabase STATIC "MigrationRunner.cpp") add_library(dDatabase STATIC "MigrationRunner.cpp")
add_custom_target(conncpp_dylib
${CMAKE_COMMAND} -E copy $<TARGET_FILE:MariaDB::ConnCpp> ${PROJECT_BINARY_DIR})
add_dependencies(dDatabase conncpp_dylib)
target_include_directories(dDatabase PUBLIC ".") target_include_directories(dDatabase PUBLIC ".")
target_link_libraries(dDatabase target_link_libraries(dDatabase
PUBLIC dDatabaseCDClient dDatabaseGame) PUBLIC dDatabaseCDClient dDatabaseGame)

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <string> #include <string>
#include <conncpp.hpp>
#include "GameDatabase.h" #include "GameDatabase.h"

View File

@ -24,6 +24,7 @@
#include "IIgnoreList.h" #include "IIgnoreList.h"
#include "IAccountsRewardCodes.h" #include "IAccountsRewardCodes.h"
#include "IBehaviors.h" #include "IBehaviors.h"
#include "IUgcModularBuild.h"
namespace sql { namespace sql {
class Statement; class Statement;
@ -42,7 +43,7 @@ class GameDatabase :
public IPropertyContents, public IProperty, public IPetNames, public ICharXml, public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo, public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList, public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList,
public IBehaviors { public IBehaviors, public IUgcModularBuild {
public: public:
virtual ~GameDatabase() = default; virtual ~GameDatabase() = default;
// TODO: These should be made private. // TODO: These should be made private.

View File

@ -3,12 +3,44 @@
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
#include <string>
#include <vector>
class ILeaderboard { class ILeaderboard {
public: public:
struct Entry {
uint32_t charId{};
uint32_t lastPlayedTimestamp{};
float primaryScore{};
float secondaryScore{};
uint32_t tertiaryScore{};
uint32_t numWins{};
uint32_t numTimesPlayed{};
uint32_t ranking{};
std::string name{};
};
struct Score {
auto operator<=>(const Score& rhs) const = default;
float primaryScore{ 0.0f };
float secondaryScore{ 0.0f };
float tertiaryScore{ 0.0f };
};
// Get the donation total for the given activity id. // Get the donation total for the given activity id.
virtual std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) = 0; virtual std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) = 0;
virtual std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) = 0;
virtual std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) = 0;
virtual std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) = 0;
virtual std::vector<ILeaderboard::Entry> GetAgsLeaderboard(const uint32_t activityId) = 0;
virtual std::optional<Score> GetPlayerScore(const uint32_t playerId, const uint32_t gameId) = 0;
virtual void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) = 0;
virtual void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) = 0;
virtual void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) = 0;
}; };
#endif //!__ILEADERBOARD__H__ #endif //!__ILEADERBOARD__H__

View File

@ -0,0 +1,14 @@
#ifndef IUGCMODULARBUILD_H
#define IUGCMODULARBUILD_H
#include <cstdint>
#include <optional>
#include <string>
class IUgcModularBuild {
public:
virtual void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) = 0;
virtual void DeleteUgcBuild(const LWOOBJID bigId) = 0;
};
#endif //!IUGCMODULARBUILD_H

View File

@ -7,6 +7,7 @@
#include "GameDatabase.h" #include "GameDatabase.h"
typedef std::unique_ptr<sql::PreparedStatement>& UniquePreppedStmtRef; typedef std::unique_ptr<sql::PreparedStatement>& UniquePreppedStmtRef;
typedef std::unique_ptr<sql::ResultSet> UniqueResultSet;
// Purposefully no definition for this to provide linker errors in the case someone tries to // Purposefully no definition for this to provide linker errors in the case someone tries to
// bind a parameter to a type that isn't defined. // bind a parameter to a type that isn't defined.
@ -113,6 +114,16 @@ public:
void RemoveBehavior(const int32_t characterId) override; void RemoveBehavior(const int32_t characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override; std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetAgsLeaderboard(const uint32_t activityId) override;
void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override;
void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override;
std::optional<ILeaderboard::Score> GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override;
void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) override;
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override;
private: private:
// Generic query functions that can be used for any query. // Generic query functions that can be used for any query.

View File

@ -20,6 +20,7 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES
"PropertyContents.cpp" "PropertyContents.cpp"
"Servers.cpp" "Servers.cpp"
"Ugc.cpp" "Ugc.cpp"
"UgcModularBuild.cpp"
PARENT_SCOPE PARENT_SCOPE
) )

View File

@ -1,5 +1,9 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
#include "Game.h"
#include "Logger.h"
#include "dConfig.h"
std::optional<uint32_t> MySQLDatabase::GetDonationTotal(const uint32_t activityId) { std::optional<uint32_t> MySQLDatabase::GetDonationTotal(const uint32_t activityId) {
auto donation_total = ExecuteSelect("SELECT SUM(primaryScore) as donation_total FROM leaderboard WHERE game_id = ?;", activityId); auto donation_total = ExecuteSelect("SELECT SUM(primaryScore) as donation_total FROM leaderboard WHERE game_id = ?;", activityId);
@ -9,3 +13,75 @@ std::optional<uint32_t> MySQLDatabase::GetDonationTotal(const uint32_t activityI
return donation_total->getUInt("donation_total"); return donation_total->getUInt("donation_total");
} }
std::vector<ILeaderboard::Entry> ProcessQuery(UniqueResultSet& rows) {
std::vector<ILeaderboard::Entry> entries;
entries.reserve(rows->rowsCount());
while (rows->next()) {
auto& entry = entries.emplace_back();
entry.charId = rows->getUInt("character_id");
entry.lastPlayedTimestamp = rows->getUInt("lp_unix");
entry.primaryScore = rows->getFloat("primaryScore");
entry.secondaryScore = rows->getFloat("secondaryScore");
entry.tertiaryScore = rows->getFloat("tertiaryScore");
entry.numWins = rows->getUInt("numWins");
entry.numTimesPlayed = rows->getUInt("timesPlayed");
entry.name = rows->getString("char_name");
// entry.ranking is never set because its calculated in leaderboard in code.
}
return entries;
}
std::vector<ILeaderboard::Entry> MySQLDatabase::GetDescendingLeaderboard(const uint32_t activityId) {
auto leaderboard = ExecuteSelect("SELECT *, UNIX_TIMESTAMP(last_played) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore DESC, tertiaryScore DESC, last_played ASC;", activityId);
return ProcessQuery(leaderboard);
}
std::vector<ILeaderboard::Entry> MySQLDatabase::GetAscendingLeaderboard(const uint32_t activityId) {
auto leaderboard = ExecuteSelect("SELECT *, UNIX_TIMESTAMP(last_played) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore ASC, secondaryscore ASC, tertiaryScore ASC, last_played ASC;", activityId);
return ProcessQuery(leaderboard);
}
std::vector<ILeaderboard::Entry> MySQLDatabase::GetAgsLeaderboard(const uint32_t activityId) {
auto query = Game::config->GetValue("classic_survival_scoring") != "1" ?
"SELECT *, UNIX_TIMESTAMP(last_played) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore DESC, tertiaryScore DESC, last_played ASC;" :
"SELECT *, UNIX_TIMESTAMP(last_played) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY secondaryscore DESC, primaryscore DESC, tertiaryScore DESC, last_played ASC;";
auto leaderboard = ExecuteSelect(query, activityId);
return ProcessQuery(leaderboard);
}
std::vector<ILeaderboard::Entry> MySQLDatabase::GetNsLeaderboard(const uint32_t activityId) {
auto leaderboard = ExecuteSelect("SELECT *, UNIX_TIMESTAMP(last_played) as lp_unix, ci.name as char_name FROM leaderboard lb JOIN charinfo ci on ci.id = lb.character_id where game_id = ? ORDER BY primaryscore DESC, secondaryscore ASC, tertiaryScore DESC, last_played ASC;", activityId);
return ProcessQuery(leaderboard);
}
void MySQLDatabase::SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) {
ExecuteInsert("INSERT leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, character_id = ?, game_id = ?;",
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
}
void MySQLDatabase::UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) {
ExecuteInsert("UPDATE leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;",
score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId);
}
std::optional<ILeaderboard::Score> MySQLDatabase::GetPlayerScore(const uint32_t playerId, const uint32_t gameId) {
std::optional<ILeaderboard::Score> toReturn = std::nullopt;
auto res = ExecuteSelect("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;", playerId, gameId);
if (res->next()) {
toReturn = ILeaderboard::Score{
.primaryScore = res->getFloat("primaryScore"),
.secondaryScore = res->getFloat("secondaryScore"),
.tertiaryScore = res->getFloat("tertiaryScore")
};
}
return toReturn;
}
void MySQLDatabase::IncrementNumWins(const uint32_t playerId, const uint32_t gameId) {
ExecuteUpdate("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId);
}

View File

@ -0,0 +1,9 @@
#include "MySQLDatabase.h"
void MySQLDatabase::InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) {
ExecuteInsert("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)", bigId, modules, characterId);
}
void MySQLDatabase::DeleteUgcBuild(const LWOOBJID bigId) {
ExecuteDelete("DELETE FROM ugc_modular_build WHERE ugc_id = ?;", bigId);
}

View File

@ -91,6 +91,16 @@ class TestSQLDatabase : public GameDatabase {
void RemoveBehavior(const int32_t behaviorId) override; void RemoveBehavior(const int32_t behaviorId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; }; std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; };
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override { return {}; };
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override { return {}; };
std::vector<ILeaderboard::Entry> GetAgsLeaderboard(const uint32_t activityId) override { return {}; };
void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override {};
void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override {};
std::optional<ILeaderboard::Score> GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override { return {}; };
void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) override {};
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override {};
void DeleteUgcBuild(const LWOOBJID bigId) override {};
}; };
#endif //!TESTSQLDATABASE_H #endif //!TESTSQLDATABASE_H

View File

@ -34,7 +34,7 @@ Migration LoadMigration(std::string path) {
void MigrationRunner::RunMigrations() { void MigrationRunner::RunMigrations() {
Database::Get()->CreateMigrationHistoryTable(); Database::Get()->CreateMigrationHistoryTable();
sql::SQLString finalSQL = ""; std::string finalSQL = "";
bool runSd0Migrations = false; bool runSd0Migrations = false;
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) { for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) {
auto migration = LoadMigration("dlu/" + entry); auto migration = LoadMigration("dlu/" + entry);
@ -61,12 +61,12 @@ void MigrationRunner::RunMigrations() {
} }
if (!finalSQL.empty()) { if (!finalSQL.empty()) {
auto migration = GeneralUtils::SplitString(static_cast<std::string>(finalSQL), ';'); auto migration = GeneralUtils::SplitString(finalSQL, ';');
for (auto& query : migration) { for (auto& query : migration) {
try { try {
if (query.empty()) continue; if (query.empty()) continue;
Database::Get()->ExecuteCustomQuery(query.c_str()); Database::Get()->ExecuteCustomQuery(query);
} catch (sql::SQLException& e) { } catch (std::exception& e) {
LOG("Encountered error running migration: %s", e.what()); LOG("Encountered error running migration: %s", e.what());
} }
} }

View File

@ -1351,6 +1351,11 @@ void Entity::OnCollisionPhantom(const LWOOBJID otherEntity) {
callback(other); callback(other);
} }
SwitchComponent* switchComp = GetComponent<SwitchComponent>();
if (switchComp) {
switchComp->OnUse(other);
}
TriggerEvent(eTriggerEventType::ENTER, other); TriggerEvent(eTriggerEventType::ENTER, other);
// POI system // POI system

View File

@ -1,5 +1,6 @@
#include "LeaderboardManager.h" #include "LeaderboardManager.h"
#include <ranges>
#include <sstream> #include <sstream>
#include <utility> #include <utility>
@ -72,197 +73,191 @@ void Leaderboard::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write0(); bitStream.Write0();
} }
void Leaderboard::QueryToLdf(std::unique_ptr<sql::ResultSet>& rows) { // Takes the resulting query from a leaderboard lookup and converts it to the LDF we need
Clear(); // to send it to a client.
if (rows->rowsCount() == 0) return; void QueryToLdf(Leaderboard& leaderboard, const std::vector<ILeaderboard::Entry>& leaderboardEntries) {
using enum Leaderboard::Type;
leaderboard.Clear();
if (leaderboardEntries.empty()) return;
this->entries.reserve(rows->rowsCount()); for (const auto& leaderboardEntry : leaderboardEntries) {
while (rows->next()) {
constexpr int32_t MAX_NUM_DATA_PER_ROW = 9; constexpr int32_t MAX_NUM_DATA_PER_ROW = 9;
this->entries.push_back(std::vector<LDFBaseData*>()); auto& entry = leaderboard.PushBackEntry();
auto& entry = this->entries.back();
entry.reserve(MAX_NUM_DATA_PER_ROW); entry.reserve(MAX_NUM_DATA_PER_ROW);
entry.push_back(new LDFData<uint64_t>(u"CharacterID", rows->getInt("character_id"))); entry.push_back(new LDFData<uint64_t>(u"CharacterID", leaderboardEntry.charId));
entry.push_back(new LDFData<uint64_t>(u"LastPlayed", rows->getUInt64("lastPlayed"))); entry.push_back(new LDFData<uint64_t>(u"LastPlayed", leaderboardEntry.lastPlayedTimestamp));
entry.push_back(new LDFData<int32_t>(u"NumPlayed", rows->getInt("timesPlayed"))); entry.push_back(new LDFData<int32_t>(u"NumPlayed", leaderboardEntry.numTimesPlayed));
entry.push_back(new LDFData<std::u16string>(u"name", GeneralUtils::ASCIIToUTF16(rows->getString("name").c_str()))); entry.push_back(new LDFData<std::u16string>(u"name", GeneralUtils::ASCIIToUTF16(leaderboardEntry.name)));
entry.push_back(new LDFData<uint64_t>(u"RowNumber", rows->getInt("ranking"))); entry.push_back(new LDFData<uint64_t>(u"RowNumber", leaderboardEntry.ranking));
switch (leaderboardType) { switch (leaderboard.GetLeaderboardType()) {
case Type::ShootingGallery: case ShootingGallery:
entry.push_back(new LDFData<int32_t>(u"Score", rows->getInt("primaryScore"))); entry.push_back(new LDFData<int32_t>(u"Score", leaderboardEntry.primaryScore));
// Score:1 // Score:1
entry.push_back(new LDFData<int32_t>(u"Streak", rows->getInt("secondaryScore"))); entry.push_back(new LDFData<int32_t>(u"Streak", leaderboardEntry.secondaryScore));
// Streak:1 // Streak:1
entry.push_back(new LDFData<float>(u"HitPercentage", (rows->getInt("tertiaryScore") / 100.0f))); entry.push_back(new LDFData<float>(u"HitPercentage", (leaderboardEntry.tertiaryScore / 100.0f)));
// HitPercentage:3 between 0 and 1 // HitPercentage:3 between 0 and 1
break; break;
case Type::Racing: case Racing:
entry.push_back(new LDFData<float>(u"BestTime", rows->getDouble("primaryScore"))); entry.push_back(new LDFData<float>(u"BestTime", leaderboardEntry.primaryScore));
// BestLapTime:3 // BestLapTime:3
entry.push_back(new LDFData<float>(u"BestLapTime", rows->getDouble("secondaryScore"))); entry.push_back(new LDFData<float>(u"BestLapTime", leaderboardEntry.secondaryScore));
// BestTime:3 // BestTime:3
entry.push_back(new LDFData<int32_t>(u"License", 1)); entry.push_back(new LDFData<int32_t>(u"License", 1));
// License:1 - 1 if player has completed mission 637 and 0 otherwise // License:1 - 1 if player has completed mission 637 and 0 otherwise
entry.push_back(new LDFData<int32_t>(u"NumWins", rows->getInt("numWins"))); entry.push_back(new LDFData<int32_t>(u"NumWins", leaderboardEntry.numWins));
// NumWins:1 // NumWins:1
break; break;
case Type::UnusedLeaderboard4: case UnusedLeaderboard4:
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore"))); entry.push_back(new LDFData<int32_t>(u"Points", leaderboardEntry.primaryScore));
// Points:1 // Points:1
break; break;
case Type::MonumentRace: case MonumentRace:
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("primaryScore"))); entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.primaryScore));
// Time:1(?) // Time:1(?)
break; break;
case Type::FootRace: case FootRace:
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("primaryScore"))); entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.primaryScore));
// Time:1 // Time:1
break; break;
case Type::Survival: case Survival:
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore"))); entry.push_back(new LDFData<int32_t>(u"Points", leaderboardEntry.primaryScore));
// Points:1 // Points:1
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("secondaryScore"))); entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.secondaryScore));
// Time:1 // Time:1
break; break;
case Type::SurvivalNS: case SurvivalNS:
entry.push_back(new LDFData<int32_t>(u"Wave", rows->getInt("primaryScore"))); entry.push_back(new LDFData<int32_t>(u"Wave", leaderboardEntry.primaryScore));
// Wave:1 // Wave:1
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("secondaryScore"))); entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.secondaryScore));
// Time:1 // Time:1
break; break;
case Type::Donations: case Donations:
entry.push_back(new LDFData<int32_t>(u"Score", rows->getInt("primaryScore"))); entry.push_back(new LDFData<int32_t>(u"Score", leaderboardEntry.primaryScore));
// Score:1 // Score:1
break; break;
case Type::None: case None:
// This type is included here simply to resolve a compiler warning on mac about unused enum types [[fallthrough]];
break;
default: default:
break; break;
} }
} }
} }
const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType) {
// Use a switch case and return desc for all 3 columns if higher is better and asc if lower is better std::vector<ILeaderboard::Entry> toReturn;
switch (leaderboardType) {
case Type::Racing: int32_t index = 0;
case Type::MonumentRace: // for friends and top, we dont need to find this players index.
return "primaryScore ASC, secondaryScore ASC, tertiaryScore ASC"; if (infoType == Leaderboard::InfoType::MyStanding || infoType == Leaderboard::InfoType::Friends) {
case Type::Survival: for (; index < leaderboard.size(); index++) {
return Game::config->GetValue("classic_survival_scoring") == "1" ? if (leaderboard[index].charId == relatedPlayer) break;
"secondaryScore DESC, primaryScore DESC, tertiaryScore DESC" : }
"primaryScore DESC, secondaryScore DESC, tertiaryScore DESC";
case Type::SurvivalNS:
return "primaryScore DESC, secondaryScore ASC, tertiaryScore DESC";
case Type::ShootingGallery:
case Type::FootRace:
case Type::UnusedLeaderboard4:
case Type::Donations:
case Type::None:
default:
return "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC";
} }
if (leaderboard.size() < 10) {
toReturn.assign(leaderboard.begin(), leaderboard.end());
index = 0;
} else if (index < 10) {
toReturn.assign(leaderboard.begin(), leaderboard.begin() + 10); // get the top 10 since we are in the top 10
index = 0;
} else if (index > leaderboard.size() - 10) {
toReturn.assign(leaderboard.end() - 10, leaderboard.end()); // get the bottom 10 since we are in the bottom 10
index = leaderboard.size() - 10;
} else {
toReturn.assign(leaderboard.begin() + index - 5, leaderboard.begin() + index + 5); // get the 5 above and below
index -= 5;
}
int32_t i = index;
for (auto& entry : toReturn) {
entry.ranking = ++i;
}
return toReturn;
} }
void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t resultEnd) { std::vector<ILeaderboard::Entry> FilterWeeklies(const std::vector<ILeaderboard::Entry>& leaderboard) {
resultStart++; // Filter the leaderboard to only include entries from the last week
resultEnd++; const auto currentTime = std::chrono::system_clock::now();
// We need everything except 1 column so i'm selecting * from leaderboard auto epochTime = currentTime.time_since_epoch().count();
const std::string queryBase = constexpr auto SECONDS_IN_A_WEEK = 60 * 60 * 24 * 7; // if you think im taking leap seconds into account thats cute.
R"QUERY(
WITH leaderboardsRanked AS (
SELECT leaderboard.*, charinfo.name,
RANK() OVER
(
ORDER BY %s, UNIX_TIMESTAMP(last_played) ASC, id DESC
) AS ranking
FROM leaderboard JOIN charinfo on charinfo.id = leaderboard.character_id
WHERE game_id = ? %s
),
myStanding AS (
SELECT
ranking as myRank
FROM leaderboardsRanked
WHERE id = ?
),
lowestRanking AS (
SELECT MAX(ranking) AS lowestRank
FROM leaderboardsRanked
)
SELECT leaderboardsRanked.*, character_id, UNIX_TIMESTAMP(last_played) as lastPlayed, leaderboardsRanked.name, leaderboardsRanked.ranking FROM leaderboardsRanked, myStanding, lowestRanking
WHERE leaderboardsRanked.ranking
BETWEEN
LEAST(GREATEST(CAST(myRank AS SIGNED) - 5, %i), CAST(lowestRanking.lowestRank AS SIGNED) - 9)
AND
LEAST(GREATEST(myRank + 5, %i), lowestRanking.lowestRank)
ORDER BY ranking ASC;
)QUERY";
std::string friendsFilter = std::vector<ILeaderboard::Entry> weeklyLeaderboard;
R"QUERY( for (const auto& entry : leaderboard) {
AND ( if (epochTime - entry.lastPlayedTimestamp < SECONDS_IN_A_WEEK) {
character_id IN ( weeklyLeaderboard.push_back(entry);
SELECT fr.requested_player FROM ( }
SELECT CASE
WHEN player_id = ? THEN friend_id
WHEN friend_id = ? THEN player_id
END AS requested_player
FROM friends
) AS fr
JOIN charinfo AS ci
ON ci.id = fr.requested_player
WHERE fr.requested_player IS NOT NULL
)
OR character_id = ?
)
)QUERY";
std::string weeklyFilter = " AND UNIX_TIMESTAMP(last_played) BETWEEN UNIX_TIMESTAMP(date_sub(now(),INTERVAL 1 WEEK)) AND UNIX_TIMESTAMP(now()) ";
std::string filter;
// Setup our filter based on the query type
if (this->infoType == InfoType::Friends) filter += friendsFilter;
if (this->weekly) filter += weeklyFilter;
const auto orderBase = GetOrdering(this->leaderboardType);
// For top query, we want to just rank all scores, but for all others we need the scores around a specific player
std::string baseLookup;
if (this->infoType == InfoType::Top) {
baseLookup = "SELECT id, last_played FROM leaderboard WHERE game_id = ? " + (this->weekly ? weeklyFilter : std::string("")) + " ORDER BY ";
baseLookup += orderBase.data();
} else {
baseLookup = "SELECT id, last_played FROM leaderboard WHERE game_id = ? " + (this->weekly ? weeklyFilter : std::string("")) + " AND character_id = ";
baseLookup += std::to_string(static_cast<uint32_t>(this->relatedPlayer));
} }
baseLookup += " LIMIT 1";
LOG_DEBUG("query is %s", baseLookup.c_str());
std::unique_ptr<sql::PreparedStatement> baseQuery(Database::Get()->CreatePreppedStmt(baseLookup));
baseQuery->setInt(1, this->gameID);
std::unique_ptr<sql::ResultSet> baseResult(baseQuery->executeQuery());
if (!baseResult->next()) return; // In this case, there are no entries in the leaderboard for this game. return weeklyLeaderboard;
}
uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id"); std::vector<ILeaderboard::Entry> FilterFriends(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer) {
// Filter the leaderboard to only include friends of the player
// Create and execute the actual save here. Using a heap allocated buffer to avoid stack overflow auto friendOfPlayer = Database::Get()->GetFriendsList(relatedPlayer);
constexpr uint16_t STRING_LENGTH = 4096; std::vector<ILeaderboard::Entry> friendsLeaderboard;
std::unique_ptr<char[]> lookupBuffer = std::make_unique<char[]>(STRING_LENGTH); for (const auto& entry : leaderboard) {
int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd); const auto res = std::ranges::find_if(friendOfPlayer, [&entry, relatedPlayer](const FriendData& data) {
DluAssert(res != -1); return entry.charId == data.friendID || entry.charId == relatedPlayer;
std::unique_ptr<sql::PreparedStatement> query(Database::Get()->CreatePreppedStmt(lookupBuffer.get())); });
LOG_DEBUG("Query is %s vars are %i %i %i", lookupBuffer.get(), this->gameID, this->relatedPlayer, relatedPlayerLeaderboardId); if (res != friendOfPlayer.cend()) {
query->setInt(1, this->gameID); friendsLeaderboard.push_back(entry);
if (this->infoType == InfoType::Friends) { }
query->setInt(2, this->relatedPlayer);
query->setInt(3, this->relatedPlayer);
query->setInt(4, this->relatedPlayer);
query->setInt(5, relatedPlayerLeaderboardId);
} else {
query->setInt(2, relatedPlayerLeaderboardId);
} }
std::unique_ptr<sql::ResultSet> result(query->executeQuery());
QueryToLdf(result); return friendsLeaderboard;
}
std::vector<ILeaderboard::Entry> ProcessLeaderboard(
const std::vector<ILeaderboard::Entry>& leaderboard,
const bool weekly,
const Leaderboard::InfoType infoType,
const uint32_t relatedPlayer) {
std::vector<ILeaderboard::Entry> toReturn;
if (infoType == Leaderboard::InfoType::Friends) {
const auto friendsLeaderboard = FilterFriends(leaderboard, relatedPlayer);
toReturn = FilterTo10(weekly ? FilterWeeklies(friendsLeaderboard) : friendsLeaderboard, relatedPlayer, infoType);
} else {
toReturn = FilterTo10(weekly ? FilterWeeklies(leaderboard) : leaderboard, relatedPlayer, infoType);
}
return toReturn;
}
void Leaderboard::SetupLeaderboard(bool weekly) {
const auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID);
std::vector<ILeaderboard::Entry> leaderboardRes;
switch (leaderboardType) {
case Type::SurvivalNS:
leaderboardRes = Database::Get()->GetNsLeaderboard(gameID);
break;
case Type::Survival:
leaderboardRes = Database::Get()->GetAgsLeaderboard(gameID);
break;
case Type::Racing:
[[fallthrough]];
case Type::MonumentRace:
leaderboardRes = Database::Get()->GetAscendingLeaderboard(gameID);
break;
case Type::ShootingGallery:
[[fallthrough]];
case Type::FootRace:
[[fallthrough]];
case Type::Donations:
[[fallthrough]];
case Type::None:
[[fallthrough]];
default:
leaderboardRes = Database::Get()->GetDescendingLeaderboard(gameID);
break;
}
const auto processedLeaderboard = ProcessLeaderboard(leaderboardRes, weekly, infoType, relatedPlayer);
QueryToLdf(*this, processedLeaderboard);
} }
void Leaderboard::Send(const LWOOBJID targetID) const { void Leaderboard::Send(const LWOOBJID targetID) const {
@ -272,129 +267,41 @@ void Leaderboard::Send(const LWOOBJID targetID) const {
} }
} }
std::string FormatInsert(const Leaderboard::Type& type, const Score& score, const bool useUpdate) {
std::string insertStatement;
if (useUpdate) {
insertStatement =
R"QUERY(
UPDATE leaderboard
SET primaryScore = %f, secondaryScore = %f, tertiaryScore = %f,
timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;
)QUERY";
} else {
insertStatement =
R"QUERY(
INSERT leaderboard SET
primaryScore = %f, secondaryScore = %f, tertiaryScore = %f,
character_id = ?, game_id = ?;
)QUERY";
}
constexpr uint16_t STRING_LENGTH = 400;
// Then fill in our score
char finishedQuery[STRING_LENGTH];
int32_t res = snprintf(finishedQuery, STRING_LENGTH, insertStatement.c_str(), score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore());
DluAssert(res != -1);
return finishedQuery;
}
void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore, const float tertiaryScore) { void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore, const float tertiaryScore) {
const Leaderboard::Type leaderboardType = GetLeaderboardType(activityId); const Leaderboard::Type leaderboardType = GetLeaderboardType(activityId);
std::unique_ptr<sql::PreparedStatement> query(Database::Get()->CreatePreppedStmt("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;")); const auto oldScore = Database::Get()->GetPlayerScore(playerID, activityId);
query->setInt(1, playerID);
query->setInt(2, activityId);
std::unique_ptr<sql::ResultSet> myScoreResult(query->executeQuery());
std::string saveQuery("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;"); ILeaderboard::Score newScore{ .primaryScore = primaryScore, .secondaryScore = secondaryScore, .tertiaryScore = tertiaryScore };
Score newScore(primaryScore, secondaryScore, tertiaryScore); if (oldScore.has_value()) {
if (myScoreResult->next()) { bool lowerScoreBetter = leaderboardType == Leaderboard::Type::Racing || leaderboardType == Leaderboard::Type::MonumentRace;
Score oldScore;
bool lowerScoreBetter = false;
switch (leaderboardType) {
// Higher score better
case Leaderboard::Type::ShootingGallery: {
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
oldScore.SetTertiaryScore(myScoreResult->getInt("tertiaryScore"));
break;
}
case Leaderboard::Type::FootRace: {
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
break;
}
case Leaderboard::Type::Survival: {
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
break;
}
case Leaderboard::Type::SurvivalNS: {
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
break;
}
case Leaderboard::Type::UnusedLeaderboard4:
case Leaderboard::Type::Donations: {
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
newScore.SetPrimaryScore(oldScore.GetPrimaryScore() + newScore.GetPrimaryScore());
break;
}
case Leaderboard::Type::Racing: {
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
// For wins we dont care about the score, just the time, so zero out the tertiary.
// Wins are updated later.
oldScore.SetTertiaryScore(0);
newScore.SetTertiaryScore(0);
lowerScoreBetter = true;
break;
}
case Leaderboard::Type::MonumentRace: {
oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
lowerScoreBetter = true;
// Do score checking here
break;
}
case Leaderboard::Type::None:
default:
LOG("Unknown leaderboard type %i for game %i. Cannot save score!", leaderboardType, activityId);
return;
}
bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore; bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore;
// Nimbus station has a weird leaderboard where we need a custom scoring system // Nimbus station has a weird leaderboard where we need a custom scoring system
if (leaderboardType == Leaderboard::Type::SurvivalNS) { if (leaderboardType == Leaderboard::Type::SurvivalNS) {
newHighScore = newScore.GetPrimaryScore() > oldScore.GetPrimaryScore() || newHighScore = newScore.primaryScore > oldScore->primaryScore ||
(newScore.GetPrimaryScore() == oldScore.GetPrimaryScore() && newScore.GetSecondaryScore() < oldScore.GetSecondaryScore()); (newScore.primaryScore == oldScore->primaryScore && newScore.secondaryScore < oldScore->secondaryScore);
} else if (leaderboardType == Leaderboard::Type::Survival && Game::config->GetValue("classic_survival_scoring") == "1") { } else if (leaderboardType == Leaderboard::Type::Survival && Game::config->GetValue("classic_survival_scoring") == "1") {
Score oldScoreFlipped(oldScore.GetSecondaryScore(), oldScore.GetPrimaryScore()); ILeaderboard::Score oldScoreFlipped{oldScore->secondaryScore, oldScore->primaryScore, oldScore->tertiaryScore};
Score newScoreFlipped(newScore.GetSecondaryScore(), newScore.GetPrimaryScore()); ILeaderboard::Score newScoreFlipped{newScore.secondaryScore, newScore.primaryScore, newScore.tertiaryScore};
newHighScore = newScoreFlipped > oldScoreFlipped; newHighScore = newScoreFlipped > oldScoreFlipped;
} }
if (newHighScore) { if (newHighScore) {
saveQuery = FormatInsert(leaderboardType, newScore, true); Database::Get()->UpdateScore(playerID, activityId, newScore);
} }
} else { } else {
saveQuery = FormatInsert(leaderboardType, newScore, false); Database::Get()->SaveScore(playerID, activityId, newScore);
} }
LOG("save query %s %i %i", saveQuery.c_str(), playerID, activityId);
std::unique_ptr<sql::PreparedStatement> saveStatement(Database::Get()->CreatePreppedStmt(saveQuery));
saveStatement->setInt(1, playerID);
saveStatement->setInt(2, activityId);
saveStatement->execute();
// track wins separately // track wins separately
if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore != 0.0f) { if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore != 0.0f) {
std::unique_ptr<sql::PreparedStatement> winUpdate(Database::Get()->CreatePreppedStmt("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;")); Database::Get()->IncrementNumWins(playerID, activityId);
winUpdate->setInt(1, playerID);
winUpdate->setInt(2, activityId);
winUpdate->execute();
} }
} }
void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart, const uint32_t resultEnd) { void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID) {
Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID)); Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID));
leaderboard.SetupLeaderboard(weekly, resultStart, resultEnd); leaderboard.SetupLeaderboard(weekly);
leaderboard.Send(targetID); leaderboard.Send(targetID);
} }

View File

@ -9,46 +9,10 @@
#include "dCommonVars.h" #include "dCommonVars.h"
#include "LDFFormat.h" #include "LDFFormat.h"
namespace sql {
class ResultSet;
};
namespace RakNet { namespace RakNet {
class BitStream; class BitStream;
}; };
class Score {
public:
Score() {
primaryScore = 0;
secondaryScore = 0;
tertiaryScore = 0;
}
Score(const float primaryScore, const float secondaryScore = 0, const float 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 float score) { primaryScore = score; }
float GetPrimaryScore() const { return primaryScore; }
void SetSecondaryScore(const float score) { secondaryScore = score; }
float GetSecondaryScore() const { return secondaryScore; }
void SetTertiaryScore(const float score) { tertiaryScore = score; }
float GetTertiaryScore() const { return tertiaryScore; }
private:
float primaryScore;
float secondaryScore;
float tertiaryScore;
};
using GameID = uint32_t; using GameID = uint32_t;
class Leaderboard { class Leaderboard {
@ -79,7 +43,7 @@ public:
/** /**
* @brief Resets the leaderboard state and frees its allocated memory * @brief Resets the leaderboard state and frees its allocated memory
* *
*/ */
void Clear(); void Clear();
@ -96,20 +60,16 @@ public:
* @param resultStart The index to start the leaderboard at. Zero indexed. * @param resultStart The index to start the leaderboard at. Zero indexed.
* @param resultEnd The index to end the leaderboard at. Zero indexed. * @param resultEnd The index to end the leaderboard at. Zero indexed.
*/ */
void SetupLeaderboard(bool weekly, uint32_t resultStart = 0, uint32_t resultEnd = 10); void SetupLeaderboard(bool weekly);
/** /**
* Sends the leaderboard to the client specified by targetID. * Sends the leaderboard to the client specified by targetID.
*/ */
void Send(const LWOOBJID targetID) const; void Send(const LWOOBJID targetID) const;
// Helper function to get the columns, ordering and insert format for a leaderboard
static const std::string_view GetOrdering(Type leaderboardType);
private:
// Takes the resulting query from a leaderboard lookup and converts it to the LDF we need
// to send it to a client.
void QueryToLdf(std::unique_ptr<sql::ResultSet>& rows);
private:
using LeaderboardEntry = std::vector<LDFBaseData*>; using LeaderboardEntry = std::vector<LDFBaseData*>;
using LeaderboardEntries = std::vector<LeaderboardEntry>; using LeaderboardEntries = std::vector<LeaderboardEntry>;
@ -119,10 +79,18 @@ private:
InfoType infoType; InfoType infoType;
Leaderboard::Type leaderboardType; Leaderboard::Type leaderboardType;
bool weekly; bool weekly;
public:
LeaderboardEntry& PushBackEntry() {
return entries.emplace_back();
}
Type GetLeaderboardType() const {
return leaderboardType;
}
}; };
namespace LeaderboardManager { namespace LeaderboardManager {
void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart = 0, const uint32_t resultEnd = 10); void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID);
void SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0); void SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0);

View File

@ -82,7 +82,6 @@ void SwitchComponent::EntityEnter(Entity* entity) {
RenderComponent::PlayAnimation(m_Parent, u"engaged"); RenderComponent::PlayAnimation(m_Parent, u"engaged");
m_PetBouncer->SetPetBouncerEnabled(true); m_PetBouncer->SetPetBouncerEnabled(true);
} else { } else {
GameMessages::SendKnockback(entity->GetObjectID(), m_Parent->GetObjectID(), m_Parent->GetObjectID(), 0.0f, NiPoint3(0.0f, 17.0f, 0.0f));
Game::entityManager->SerializeEntity(m_Parent); Game::entityManager->SerializeEntity(m_Parent);
} }

View File

@ -1691,7 +1691,7 @@ void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream
bool weekly = inStream.ReadBit(); bool weekly = inStream.ReadBit();
LeaderboardManager::SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID(), resultsStart, resultsEnd); LeaderboardManager::SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID());
} }
void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream& inStream, Entity* entity) { void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream& inStream, Entity* entity) {
@ -5066,9 +5066,7 @@ void GameMessages::HandleModularBuildConvertModel(RakNet::BitStream& inStream, E
item->Disassemble(TEMP_MODELS); item->Disassemble(TEMP_MODELS);
std::unique_ptr<sql::PreparedStatement> stmt(Database::Get()->CreatePreppedStmt("DELETE FROM ugc_modular_build where ugc_id = ?")); Database::Get()->DeleteUgcBuild(item->GetSubKey());
stmt->setUInt64(1, item->GetSubKey());
stmt->execute();
item->SetCount(item->GetCount() - 1, false, false, true, eLootSourceType::QUICKBUILD); item->SetCount(item->GetCount() - 1, false, false, true, eLootSourceType::QUICKBUILD);
} }
@ -5394,6 +5392,8 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En
const auto itemType = static_cast<eItemType>(item->GetInfo().itemType); const auto itemType = static_cast<eItemType>(item->GetInfo().itemType);
if (itemType == eItemType::MODEL || itemType == eItemType::LOOT_MODEL) { if (itemType == eItemType::MODEL || itemType == eItemType::LOOT_MODEL) {
item->DisassembleModel(iStackCount); item->DisassembleModel(iStackCount);
} else if (itemType == eItemType::VEHICLE) {
Database::Get()->DeleteUgcBuild(item->GetSubKey());
} }
auto lot = item->GetLot(); auto lot = item->GetLot();
item->SetCount(item->GetCount() - iStackCount, true); item->SetCount(item->GetCount() - iStackCount, true);
@ -5569,12 +5569,8 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig); inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
} }
std::unique_ptr<sql::PreparedStatement> stmt(Database::Get()->CreatePreppedStmt("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)"));
stmt->setUInt64(1, newIdBig);
stmt->setString(2, GeneralUtils::UTF16ToWTF8(modules).c_str());
auto* pCharacter = character->GetCharacter(); auto* pCharacter = character->GetCharacter();
pCharacter ? stmt->setUInt(3, pCharacter->GetID()) : stmt->setNull(3, sql::DataType::BIGINT); Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newIdBig, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt);
stmt->execute();
auto* missionComponent = character->GetComponent<MissionComponent>(); auto* missionComponent = character->GetComponent<MissionComponent>();

View File

@ -103,7 +103,7 @@ int main(int argc, char** argv) {
//Connect to the MySQL Database //Connect to the MySQL Database
try { try {
Database::Connect(); Database::Connect();
} catch (sql::SQLException& ex) { } catch (std::exception& ex) {
LOG("Got an error while connecting to the database: %s", ex.what()); LOG("Got an error while connecting to the database: %s", ex.what());
LOG("Migrations not run"); LOG("Migrations not run");
return EXIT_FAILURE; return EXIT_FAILURE;
@ -264,7 +264,7 @@ int main(int argc, char** argv) {
//Create account //Create account
try { try {
Database::Get()->InsertNewAccount(username, std::string(hash, BCRYPT_HASHSIZE)); Database::Get()->InsertNewAccount(username, std::string(hash, BCRYPT_HASHSIZE));
} catch (sql::SQLException& e) { } catch (std::exception& e) {
LOG("A SQL error occurred!:\n %s", e.what()); LOG("A SQL error occurred!:\n %s", e.what());
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -24,9 +24,9 @@ void PersistentIDManager::Initialize() {
LOG("Invalid persistent object ID in database. Aborting to prevent bad id generation."); LOG("Invalid persistent object ID in database. Aborting to prevent bad id generation.");
throw std::runtime_error("Invalid persistent object ID in database. Aborting to prevent bad id generation."); throw std::runtime_error("Invalid persistent object ID in database. Aborting to prevent bad id generation.");
} }
} catch (sql::SQLException& e) { } catch (std::exception& e) {
LOG("Unable to fetch max persistent object ID in use. This will cause issues. Aborting to prevent collisions."); LOG("Unable to fetch max persistent object ID in use. This will cause issues. Aborting to prevent collisions.");
LOG("SQL error: %s", e.what()); LOG("Error: %s", e.what());
throw e; throw e;
} }
} }

View File

@ -121,7 +121,7 @@ void ActivityManager::GetLeaderboardData(Entity* self, const LWOOBJID playerID,
auto* sac = self->GetComponent<ScriptedActivityComponent>(); auto* sac = self->GetComponent<ScriptedActivityComponent>();
uint32_t gameID = sac != nullptr ? sac->GetActivityID() : self->GetLOT(); uint32_t gameID = sac != nullptr ? sac->GetActivityID() : self->GetLOT();
// Save the new score to the leaderboard and show the leaderboard to the player // Save the new score to the leaderboard and show the leaderboard to the player
LeaderboardManager::SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, self->GetObjectID(), 0, numResults); LeaderboardManager::SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, self->GetObjectID());
} }
void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerName, const float_t updateInterval, void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerName, const float_t updateInterval,

View File

@ -194,7 +194,7 @@ int main(int argc, char** argv) {
//Connect to the MySQL Database: //Connect to the MySQL Database:
try { try {
Database::Connect(); Database::Connect();
} catch (sql::SQLException& ex) { } catch (std::exception& ex) {
LOG("Got an error while connecting to the database: %s", ex.what()); LOG("Got an error while connecting to the database: %s", ex.what());
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -4,15 +4,6 @@ enable_testing()
find_package(GoogleTest REQUIRED) find_package(GoogleTest REQUIRED)
include(GoogleTest) include(GoogleTest)
if(APPLE)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH True)
set(CMAKE_BUILD_WITH_INSTALL_RPATH True)
set(CMAKE_INSTALL_RPATH "@executable_path")
endif()
add_custom_target(conncpp_tests
${CMAKE_COMMAND} -E copy $<TARGET_FILE:MariaDB::ConnCpp> ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# Add the subdirectories # Add the subdirectories
add_subdirectory(dCommonTests) add_subdirectory(dCommonTests)
add_subdirectory(dGameTests) add_subdirectory(dGameTests)

View File

@ -17,18 +17,13 @@ list(APPEND DCOMMONTEST_SOURCES ${DENUMS_TESTS})
# Set our executable # Set our executable
add_executable(dCommonTests ${DCOMMONTEST_SOURCES}) add_executable(dCommonTests ${DCOMMONTEST_SOURCES})
add_dependencies(dCommonTests conncpp_tests)
# Apple needs some special linkage for the mariadb connector for tests. # Needs to be in binary dir for ctest
if(APPLE) if(APPLE)
add_custom_command(TARGET dCommonTests POST_BUILD add_custom_target(dCommonTestsLink
COMMAND otool ARGS -l dCommonTests ${CMAKE_COMMAND} -E copy $<TARGET_FILE:MariaDB::ConnCpp> ${CMAKE_CURRENT_BINARY_DIR})
COMMAND otool ARGS -L dCommonTests
COMMAND ls add_dependencies(dCommonTests dCommonTestsLink)
COMMAND otool ARGS -D libmariadbcpp.dylib
COMMAND install_name_tool ARGS -change libmariadbcpp.dylib @rpath/libmariadbcpp.dylib dCommonTests
COMMAND otool ARGS -L dCommonTests
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
endif() endif()
# Link needed libraries # Link needed libraries

View File

@ -13,14 +13,12 @@ file(COPY ${COMPONENT_TEST_DATA} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
# Add the executable. Remember to add all tests above this! # Add the executable. Remember to add all tests above this!
add_executable(dGameTests ${DGAMETEST_SOURCES}) add_executable(dGameTests ${DGAMETEST_SOURCES})
add_dependencies(dGameTests conncpp_tests)
# Apple needs some special linkage for the mariadb connector for tests.
if(APPLE) if(APPLE)
add_custom_command(TARGET dGameTests POST_BUILD add_custom_target(dGameTestsLink
COMMAND install_name_tool ARGS -change libmariadbcpp.dylib @rpath/libmariadbcpp.dylib dGameTests ${CMAKE_COMMAND} -E copy $<TARGET_FILE:MariaDB::ConnCpp> ${CMAKE_CURRENT_BINARY_DIR})
COMMAND otool ARGS -L dGameTests
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) add_dependencies(dGameTests dGameTestsLink)
endif() endif()
target_link_libraries(dGameTests ${COMMON_LIBRARIES} GTest::gtest_main target_link_libraries(dGameTests ${COMMON_LIBRARIES} GTest::gtest_main

143227
thirdparty/SQLite/sqlite3.c vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff