Compare commits

..

3 Commits

Author SHA1 Message Date
David Markowitz
c7d01dbb82 Merge branch 'main' into add-sqlite 2024-12-04 00:43:43 -08:00
David Markowitz
9e242995e9 use SQLite types 2024-11-15 01:05:05 -08:00
David Markowitz
0aa1f70540 Use less specific matcher 2024-11-14 12:43:52 -08:00
48 changed files with 41816 additions and 116413 deletions

View File

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

1
.gitignore vendored
View File

@@ -95,7 +95,6 @@ ipch/
# Exceptions:
CMakeSettings.json
CMakeUserPresets.json
*.vcxproj
*.filters
*.cmake

View File

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

View File

@@ -11,6 +11,9 @@
"displayName": "Default configure step",
"description": "Use 'build' dir and Unix makefiles",
"binaryDir": "${sourceDir}/build",
"environment": {
"DLU_CONFIG_DIR": "${sourceDir}/build"
},
"generator": "Unix Makefiles"
},
{

View File

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

View File

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

View File

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

View File

@@ -2,12 +2,6 @@ add_subdirectory(CDClientDatabase)
add_subdirectory(GameDatabase)
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_link_libraries(dDatabase
PUBLIC dDatabaseCDClient dDatabaseGame)

View File

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

View File

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

View File

@@ -3,45 +3,12 @@
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
class ILeaderboard {
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.
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;
virtual void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) = 0;
};
#endif //!__ILEADERBOARD__H__

View File

@@ -1,14 +0,0 @@
#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,7 +7,6 @@
#include "GameDatabase.h"
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
// bind a parameter to a type that isn't defined.
@@ -114,17 +113,6 @@ public:
void RemoveBehavior(const int32_t characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) 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 IncrementTimesPlayed(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:
// Generic query functions that can be used for any query.

View File

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

View File

@@ -1,9 +1,5 @@
#include "MySQLDatabase.h"
#include "Game.h"
#include "Logger.h"
#include "dConfig.h"
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);
@@ -13,79 +9,3 @@ std::optional<uint32_t> MySQLDatabase::GetDonationTotal(const uint32_t activityI
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);
}
void MySQLDatabase::IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) {
ExecuteUpdate("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;", 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

@@ -1,9 +0,0 @@
#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,17 +91,6 @@ class TestSQLDatabase : public GameDatabase {
void RemoveBehavior(const int32_t behaviorId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
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 IncrementTimesPlayed(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

View File

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

View File

@@ -83,7 +83,6 @@
#include "ItemComponent.h"
#include "GhostComponent.h"
#include "AchievementVendorComponent.h"
#include "VanityUtilities.h"
// Table includes
#include "CDComponentsRegistryTable.h"
@@ -1272,7 +1271,6 @@ void Entity::Update(const float deltaTime) {
auto timerName = timer.GetName();
m_Timers.erase(m_Timers.begin() + timerPosition);
GetScript()->OnTimerDone(this, timerName);
VanityUtilities::OnTimerDone(this, timerName);
TriggerEvent(eTriggerEventType::TIMER_DONE, this);
} else {
@@ -1336,7 +1334,6 @@ void Entity::OnCollisionProximity(LWOOBJID otherEntity, const std::string& proxN
if (!other) return;
GetScript()->OnProximityUpdate(this, other, proxName, status);
VanityUtilities::OnProximityUpdate(this, other, proxName, status);
RocketLaunchpadControlComponent* rocketComp = GetComponent<RocketLaunchpadControlComponent>();
if (!rocketComp) return;
@@ -1354,11 +1351,6 @@ void Entity::OnCollisionPhantom(const LWOOBJID otherEntity) {
callback(other);
}
SwitchComponent* switchComp = GetComponent<SwitchComponent>();
if (switchComp) {
switchComp->OnUse(other);
}
TriggerEvent(eTriggerEventType::ENTER, other);
// POI system

View File

@@ -505,7 +505,6 @@ void EntityManager::UpdateGhosting(Entity* player) {
if (collectionId != 0) {
collectionId = static_cast<uint32_t>(collectionId) + static_cast<uint32_t>(Game::server->GetZoneID() << 8);
if (missionComponent->HasCollectible(collectionId)) {
continue;
}

View File

@@ -1,6 +1,5 @@
#include "LeaderboardManager.h"
#include <ranges>
#include <sstream>
#include <utility>
@@ -73,191 +72,197 @@ void Leaderboard::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write0();
}
// Takes the resulting query from a leaderboard lookup and converts it to the LDF we need
// to send it to a client.
void QueryToLdf(Leaderboard& leaderboard, const std::vector<ILeaderboard::Entry>& leaderboardEntries) {
using enum Leaderboard::Type;
leaderboard.Clear();
if (leaderboardEntries.empty()) return;
void Leaderboard::QueryToLdf(std::unique_ptr<sql::ResultSet>& rows) {
Clear();
if (rows->rowsCount() == 0) return;
for (const auto& leaderboardEntry : leaderboardEntries) {
this->entries.reserve(rows->rowsCount());
while (rows->next()) {
constexpr int32_t MAX_NUM_DATA_PER_ROW = 9;
auto& entry = leaderboard.PushBackEntry();
this->entries.push_back(std::vector<LDFBaseData*>());
auto& entry = this->entries.back();
entry.reserve(MAX_NUM_DATA_PER_ROW);
entry.push_back(new LDFData<uint64_t>(u"CharacterID", leaderboardEntry.charId));
entry.push_back(new LDFData<uint64_t>(u"LastPlayed", leaderboardEntry.lastPlayedTimestamp));
entry.push_back(new LDFData<int32_t>(u"NumPlayed", leaderboardEntry.numTimesPlayed));
entry.push_back(new LDFData<std::u16string>(u"name", GeneralUtils::ASCIIToUTF16(leaderboardEntry.name)));
entry.push_back(new LDFData<uint64_t>(u"RowNumber", leaderboardEntry.ranking));
switch (leaderboard.GetLeaderboardType()) {
case ShootingGallery:
entry.push_back(new LDFData<int32_t>(u"Score", leaderboardEntry.primaryScore));
entry.push_back(new LDFData<uint64_t>(u"CharacterID", rows->getInt("character_id")));
entry.push_back(new LDFData<uint64_t>(u"LastPlayed", rows->getUInt64("lastPlayed")));
entry.push_back(new LDFData<int32_t>(u"NumPlayed", rows->getInt("timesPlayed")));
entry.push_back(new LDFData<std::u16string>(u"name", GeneralUtils::ASCIIToUTF16(rows->getString("name").c_str())));
entry.push_back(new LDFData<uint64_t>(u"RowNumber", rows->getInt("ranking")));
switch (leaderboardType) {
case Type::ShootingGallery:
entry.push_back(new LDFData<int32_t>(u"Score", rows->getInt("primaryScore")));
// Score:1
entry.push_back(new LDFData<int32_t>(u"Streak", leaderboardEntry.secondaryScore));
entry.push_back(new LDFData<int32_t>(u"Streak", rows->getInt("secondaryScore")));
// Streak:1
entry.push_back(new LDFData<float>(u"HitPercentage", (leaderboardEntry.tertiaryScore / 100.0f)));
entry.push_back(new LDFData<float>(u"HitPercentage", (rows->getInt("tertiaryScore") / 100.0f)));
// HitPercentage:3 between 0 and 1
break;
case Racing:
entry.push_back(new LDFData<float>(u"BestTime", leaderboardEntry.primaryScore));
case Type::Racing:
entry.push_back(new LDFData<float>(u"BestTime", rows->getDouble("primaryScore")));
// BestLapTime:3
entry.push_back(new LDFData<float>(u"BestLapTime", leaderboardEntry.secondaryScore));
entry.push_back(new LDFData<float>(u"BestLapTime", rows->getDouble("secondaryScore")));
// BestTime:3
entry.push_back(new LDFData<int32_t>(u"License", 1));
// License:1 - 1 if player has completed mission 637 and 0 otherwise
entry.push_back(new LDFData<int32_t>(u"NumWins", leaderboardEntry.numWins));
entry.push_back(new LDFData<int32_t>(u"NumWins", rows->getInt("numWins")));
// NumWins:1
break;
case UnusedLeaderboard4:
entry.push_back(new LDFData<int32_t>(u"Points", leaderboardEntry.primaryScore));
case Type::UnusedLeaderboard4:
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore")));
// Points:1
break;
case MonumentRace:
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.primaryScore));
case Type::MonumentRace:
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("primaryScore")));
// Time:1(?)
break;
case FootRace:
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.primaryScore));
case Type::FootRace:
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("primaryScore")));
// Time:1
break;
case Survival:
entry.push_back(new LDFData<int32_t>(u"Points", leaderboardEntry.primaryScore));
case Type::Survival:
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore")));
// Points:1
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.secondaryScore));
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("secondaryScore")));
// Time:1
break;
case SurvivalNS:
entry.push_back(new LDFData<int32_t>(u"Wave", leaderboardEntry.primaryScore));
case Type::SurvivalNS:
entry.push_back(new LDFData<int32_t>(u"Wave", rows->getInt("primaryScore")));
// Wave:1
entry.push_back(new LDFData<int32_t>(u"Time", leaderboardEntry.secondaryScore));
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("secondaryScore")));
// Time:1
break;
case Donations:
entry.push_back(new LDFData<int32_t>(u"Score", leaderboardEntry.primaryScore));
case Type::Donations:
entry.push_back(new LDFData<int32_t>(u"Score", rows->getInt("primaryScore")));
// Score:1
break;
case None:
[[fallthrough]];
case Type::None:
// This type is included here simply to resolve a compiler warning on mac about unused enum types
break;
default:
break;
}
}
}
std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType) {
std::vector<ILeaderboard::Entry> toReturn;
int32_t index = 0;
// for friends and top, we dont need to find this players index.
if (infoType == Leaderboard::InfoType::MyStanding || infoType == Leaderboard::InfoType::Friends) {
for (; index < leaderboard.size(); index++) {
if (leaderboard[index].charId == relatedPlayer) break;
}
}
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;
}
std::vector<ILeaderboard::Entry> FilterWeeklies(const std::vector<ILeaderboard::Entry>& leaderboard) {
// Filter the leaderboard to only include entries from the last week
const auto currentTime = std::chrono::system_clock::now();
auto epochTime = currentTime.time_since_epoch().count();
constexpr auto SECONDS_IN_A_WEEK = 60 * 60 * 24 * 7; // if you think im taking leap seconds into account thats cute.
std::vector<ILeaderboard::Entry> weeklyLeaderboard;
for (const auto& entry : leaderboard) {
if (epochTime - entry.lastPlayedTimestamp < SECONDS_IN_A_WEEK) {
weeklyLeaderboard.push_back(entry);
}
}
return weeklyLeaderboard;
}
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
auto friendOfPlayer = Database::Get()->GetFriendsList(relatedPlayer);
std::vector<ILeaderboard::Entry> friendsLeaderboard;
for (const auto& entry : leaderboard) {
const auto res = std::ranges::find_if(friendOfPlayer, [&entry, relatedPlayer](const FriendData& data) {
return entry.charId == data.friendID || entry.charId == relatedPlayer;
});
if (res != friendOfPlayer.cend()) {
friendsLeaderboard.push_back(entry);
}
}
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;
const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) {
// Use a switch case and return desc for all 3 columns if higher is better and asc if lower is better
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;
return "primaryScore ASC, secondaryScore ASC, tertiaryScore ASC";
case Type::Survival:
return Game::config->GetValue("classic_survival_scoring") == "1" ?
"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:
[[fallthrough]];
case Type::FootRace:
[[fallthrough]];
case Type::UnusedLeaderboard4:
case Type::Donations:
[[fallthrough]];
case Type::None:
[[fallthrough]];
default:
leaderboardRes = Database::Get()->GetDescendingLeaderboard(gameID);
break;
return "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC";
}
}
const auto processedLeaderboard = ProcessLeaderboard(leaderboardRes, weekly, infoType, relatedPlayer);
void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t resultEnd) {
resultStart++;
resultEnd++;
// We need everything except 1 column so i'm selecting * from leaderboard
const std::string queryBase =
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";
QueryToLdf(*this, processedLeaderboard);
std::string friendsFilter =
R"QUERY(
AND (
character_id IN (
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.
uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id");
// Create and execute the actual save here. Using a heap allocated buffer to avoid stack overflow
constexpr uint16_t STRING_LENGTH = 4096;
std::unique_ptr<char[]> lookupBuffer = std::make_unique<char[]>(STRING_LENGTH);
int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd);
DluAssert(res != -1);
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);
query->setInt(1, this->gameID);
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);
}
void Leaderboard::Send(const LWOOBJID targetID) const {
@@ -267,43 +272,129 @@ 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) {
const Leaderboard::Type leaderboardType = GetLeaderboardType(activityId);
const auto oldScore = Database::Get()->GetPlayerScore(playerID, activityId);
std::unique_ptr<sql::PreparedStatement> query(Database::Get()->CreatePreppedStmt("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;"));
query->setInt(1, playerID);
query->setInt(2, activityId);
std::unique_ptr<sql::ResultSet> myScoreResult(query->executeQuery());
ILeaderboard::Score newScore{ .primaryScore = primaryScore, .secondaryScore = secondaryScore, .tertiaryScore = tertiaryScore };
if (oldScore.has_value()) {
bool lowerScoreBetter = leaderboardType == Leaderboard::Type::Racing || leaderboardType == Leaderboard::Type::MonumentRace;
std::string saveQuery("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;");
Score newScore(primaryScore, secondaryScore, tertiaryScore);
if (myScoreResult->next()) {
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;
// Nimbus station has a weird leaderboard where we need a custom scoring system
if (leaderboardType == Leaderboard::Type::SurvivalNS) {
newHighScore = newScore.primaryScore > oldScore->primaryScore ||
(newScore.primaryScore == oldScore->primaryScore && newScore.secondaryScore < oldScore->secondaryScore);
newHighScore = newScore.GetPrimaryScore() > oldScore.GetPrimaryScore() ||
(newScore.GetPrimaryScore() == oldScore.GetPrimaryScore() && newScore.GetSecondaryScore() < oldScore.GetSecondaryScore());
} else if (leaderboardType == Leaderboard::Type::Survival && Game::config->GetValue("classic_survival_scoring") == "1") {
ILeaderboard::Score oldScoreFlipped{oldScore->secondaryScore, oldScore->primaryScore, oldScore->tertiaryScore};
ILeaderboard::Score newScoreFlipped{newScore.secondaryScore, newScore.primaryScore, newScore.tertiaryScore};
Score oldScoreFlipped(oldScore.GetSecondaryScore(), oldScore.GetPrimaryScore());
Score newScoreFlipped(newScore.GetSecondaryScore(), newScore.GetPrimaryScore());
newHighScore = newScoreFlipped > oldScoreFlipped;
}
if (newHighScore) {
Database::Get()->UpdateScore(playerID, activityId, newScore);
} else {
Database::Get()->IncrementTimesPlayed(playerID, activityId);
saveQuery = FormatInsert(leaderboardType, newScore, true);
}
} else {
Database::Get()->SaveScore(playerID, activityId, newScore);
saveQuery = FormatInsert(leaderboardType, newScore, false);
}
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
if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore != 0.0f) {
Database::Get()->IncrementNumWins(playerID, activityId);
std::unique_ptr<sql::PreparedStatement> winUpdate(Database::Get()->CreatePreppedStmt("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;"));
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) {
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) {
Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID));
leaderboard.SetupLeaderboard(weekly);
leaderboard.SetupLeaderboard(weekly, resultStart, resultEnd);
leaderboard.Send(targetID);
}

View File

@@ -9,10 +9,46 @@
#include "dCommonVars.h"
#include "LDFFormat.h"
namespace sql {
class ResultSet;
};
namespace RakNet {
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;
class Leaderboard {
@@ -43,7 +79,7 @@ public:
/**
* @brief Resets the leaderboard state and frees its allocated memory
*
*
*/
void Clear();
@@ -60,16 +96,20 @@ public:
* @param resultStart The index to start the leaderboard at. Zero indexed.
* @param resultEnd The index to end the leaderboard at. Zero indexed.
*/
void SetupLeaderboard(bool weekly);
void SetupLeaderboard(bool weekly, uint32_t resultStart = 0, uint32_t resultEnd = 10);
/**
* Sends the leaderboard to the client specified by targetID.
*/
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);
using LeaderboardEntry = std::vector<LDFBaseData*>;
using LeaderboardEntries = std::vector<LeaderboardEntry>;
@@ -79,18 +119,10 @@ private:
InfoType infoType;
Leaderboard::Type leaderboardType;
bool weekly;
public:
LeaderboardEntry& PushBackEntry() {
return entries.emplace_back();
}
Type GetLeaderboardType() const {
return leaderboardType;
}
};
namespace LeaderboardManager {
void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID);
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 SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0);

View File

@@ -38,7 +38,7 @@ void ProximityMonitorComponent::SetProximityRadius(dpEntity* entity, const std::
m_ProximitiesData.insert(std::make_pair(name, entity));
}
const std::unordered_set<LWOOBJID>& ProximityMonitorComponent::GetProximityObjects(const std::string& name) const {
const std::unordered_set<LWOOBJID>& ProximityMonitorComponent::GetProximityObjects(const std::string& name) {
const auto iter = m_ProximitiesData.find(name);
if (iter == m_ProximitiesData.cend()) {

View File

@@ -46,7 +46,7 @@ public:
* @param name the proximity name to retrieve physics objects for
* @return a set of physics entity object IDs for this name
*/
const std::unordered_set<LWOOBJID>& GetProximityObjects(const std::string& name) const;
const std::unordered_set<LWOOBJID>& GetProximityObjects(const std::string& name);
/**
* Checks if the passed object is in proximity of the named proximity sensor

View File

@@ -82,6 +82,7 @@ void SwitchComponent::EntityEnter(Entity* entity) {
RenderComponent::PlayAnimation(m_Parent, u"engaged");
m_PetBouncer->SetPetBouncerEnabled(true);
} 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);
}

View File

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

View File

@@ -59,9 +59,9 @@ void VanityUtilities::SpawnVanity() {
for (const auto& npc : objects) {
if (npc.m_ID == LWOOBJID_EMPTY) continue;
if (npc.m_LOT == 176) {
if (npc.m_LOT == 176){
Game::zoneManager->RemoveSpawner(npc.m_ID);
} else {
} else{
auto* entity = Game::entityManager->GetEntity(npc.m_ID);
if (!entity) continue;
entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT);
@@ -86,14 +86,14 @@ void VanityUtilities::SpawnVanity() {
float rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
if (location.m_Chance < rate) continue;
if (object.m_LOT == 176) {
if (object.m_LOT == 176){
object.m_ID = SpawnSpawner(object, location);
} else {
// Spawn the NPC
auto* objectEntity = SpawnObject(object, location);
if (!objectEntity) continue;
object.m_ID = objectEntity->GetObjectID();
if (!object.m_Phrases.empty()) {
if (!object.m_Phrases.empty()){
objectEntity->SetVar<std::vector<std::string>>(u"chats", object.m_Phrases);
SetupNPCTalk(objectEntity);
}
@@ -107,7 +107,7 @@ LWOOBJID SpawnSpawner(const VanityObject& object, const VanityObjectLocation& lo
// guratantee we have no collisions
do {
obj.id = ObjectIDManager::GenerateObjectID();
} while (Game::zoneManager->GetSpawner(obj.id));
} while(Game::zoneManager->GetSpawner(obj.id));
obj.position = location.m_Position;
obj.rotation = location.m_Rotation;
obj.settings = object.m_Config;
@@ -146,7 +146,7 @@ Entity* SpawnObject(const VanityObject& object, const VanityObjectLocation& loca
}
void ParseXml(const std::string& file) {
if (loadedFiles.contains(file)) {
if (loadedFiles.contains(file)){
LOG("Trying to load vanity file %s twice!!!", file.c_str());
return;
}
@@ -232,7 +232,7 @@ void ParseXml(const std::string& file) {
auto* configElement = object->FirstChildElement("config");
std::vector<std::u16string> keys = {};
std::vector<LDFBaseData*> config = {};
if (configElement) {
if(configElement) {
for (auto* key = configElement->FirstChildElement("key"); key != nullptr;
key = key->NextSiblingElement("key")) {
// Get the config data
@@ -240,7 +240,7 @@ void ParseXml(const std::string& file) {
if (!data) continue;
LDFBaseData* configData = LDFBaseData::DataFromString(data);
if (configData->GetKey() == u"useLocationsAsRandomSpawnPoint" && configData->GetValueType() == eLDFType::LDF_TYPE_BOOLEAN) {
if (configData->GetKey() == u"useLocationsAsRandomSpawnPoint" && configData->GetValueType() == eLDFType::LDF_TYPE_BOOLEAN){
useLocationsAsRandomSpawnPoint = static_cast<bool>(configData);
continue;
}
@@ -250,7 +250,7 @@ void ParseXml(const std::string& file) {
}
if (!keys.empty()) config.push_back(new LDFData<std::vector<std::u16string>>(u"syncLDF", keys));
VanityObject objectData{
VanityObject objectData {
.m_Name = name,
.m_LOT = lot,
.m_Equipment = inventory,
@@ -288,7 +288,7 @@ void ParseXml(const std::string& file) {
continue;
}
VanityObjectLocation locationData{
VanityObjectLocation locationData {
.m_Position = { x.value(), y.value(), z.value() },
.m_Rotation = { rw.value(), rx.value(), ry.value(), rz.value() },
};
@@ -403,39 +403,26 @@ void SetupNPCTalk(Entity* npc) {
npc->SetProximityRadius(20.0f, "talk");
}
void VanityUtilities::OnProximityUpdate(Entity* entity, Entity* other, const std::string& proxName, const std::string& name) {
if (proxName != "talk") return;
const auto* const proximityMonitorComponent = entity->GetComponent<ProximityMonitorComponent>();
if (!proximityMonitorComponent) return;
if (name == "ENTER" && !entity->HasTimer("talk")) {
NPCTalk(entity);
}
}
void VanityUtilities::OnTimerDone(Entity* npc, const std::string& name) {
if (name == "talk") {
const auto* const proximityMonitorComponent = npc->GetComponent<ProximityMonitorComponent>();
if (!proximityMonitorComponent || proximityMonitorComponent->GetProximityObjects("talk").empty()) return;
NPCTalk(npc);
}
}
void NPCTalk(Entity* npc) {
const auto& chats = npc->GetVar<std::vector<std::string>>(u"chats");
auto* proximityMonitorComponent = npc->GetComponent<ProximityMonitorComponent>();
if (chats.empty()) return;
if (!proximityMonitorComponent->GetProximityObjects("talk").empty()) {
const auto& chats = npc->GetVar<std::vector<std::string>>(u"chats");
const auto& selected
= chats[GeneralUtils::GenerateRandomNumber<int32_t>(0, static_cast<int32_t>(chats.size() - 1))];
if (chats.empty()) {
return;
}
GameMessages::SendNotifyClientZoneObject(
npc->GetObjectID(), u"sendToclient_bubble", 0, 0, npc->GetObjectID(), selected, UNASSIGNED_SYSTEM_ADDRESS);
const auto& selected
= chats[GeneralUtils::GenerateRandomNumber<int32_t>(0, static_cast<int32_t>(chats.size() - 1))];
GameMessages::SendNotifyClientZoneObject(
npc->GetObjectID(), u"sendToclient_bubble", 0, 0, npc->GetObjectID(), selected, UNASSIGNED_SYSTEM_ADDRESS);
}
Game::entityManager->SerializeEntity(npc);
const float nextTime = GeneralUtils::GenerateRandomNumber<float>(15, 60);
npc->AddTimer("talk", nextTime);
npc->AddCallbackTimer(nextTime, [npc]() { NPCTalk(npc); });
}

View File

@@ -31,8 +31,4 @@ namespace VanityUtilities {
std::string ParseMarkdown(
const std::string& file
);
void OnProximityUpdate(Entity* entity, Entity* other, const std::string& proxName, const std::string& name);
void OnTimerDone(Entity* entity, const std::string& name);
};

View File

@@ -103,7 +103,7 @@ int main(int argc, char** argv) {
//Connect to the MySQL Database
try {
Database::Connect();
} catch (std::exception& ex) {
} catch (sql::SQLException& ex) {
LOG("Got an error while connecting to the database: %s", ex.what());
LOG("Migrations not run");
return EXIT_FAILURE;
@@ -264,7 +264,7 @@ int main(int argc, char** argv) {
//Create account
try {
Database::Get()->InsertNewAccount(username, std::string(hash, BCRYPT_HASHSIZE));
} catch (std::exception& e) {
} catch (sql::SQLException& e) {
LOG("A SQL error occurred!:\n %s", e.what());
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.");
throw std::runtime_error("Invalid persistent object ID in database. Aborting to prevent bad id generation.");
}
} catch (std::exception& e) {
} catch (sql::SQLException& e) {
LOG("Unable to fetch max persistent object ID in use. This will cause issues. Aborting to prevent collisions.");
LOG("Error: %s", e.what());
LOG("SQL error: %s", e.what());
throw e;
}
}

View File

@@ -121,7 +121,7 @@ void ActivityManager::GetLeaderboardData(Entity* self, const LWOOBJID playerID,
auto* sac = self->GetComponent<ScriptedActivityComponent>();
uint32_t gameID = sac != nullptr ? sac->GetActivityID() : self->GetLOT();
// Save the new score to the leaderboard and show the leaderboard to the player
LeaderboardManager::SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, self->GetObjectID());
LeaderboardManager::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

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

View File

@@ -1,152 +1,152 @@
CREATE TABLE IF NOT EXISTS accounts (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(35) NOT NULL UNIQUE,
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
gm_level INT UNSIGNED NOT NULL DEFAULT 0,
locked BOOLEAN NOT NULL DEFAULT FALSE,
banned BOOLEAN NOT NULL DEFAULT FALSE,
play_key_id INT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
mute_expire BIGINT UNSIGNED NOT NULL DEFAULT 0
gm_level BIGINT NOT NULL DEFAULT 0,
locked INTEGER NOT NULL DEFAULT FALSE,
banned INTEGER NOT NULL DEFAULT FALSE,
play_key_id INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
mute_expire BIGINT NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS charinfo (
id BIGINT NOT NULL PRIMARY KEY,
account_id INT NOT NULL REFERENCES accounts(id),
name VARCHAR(35) NOT NULL,
pending_name VARCHAR(35) NOT NULL,
needs_rename BOOLEAN NOT NULL DEFAULT FALSE,
prop_clone_id BIGINT UNSIGNED AUTO_INCREMENT UNIQUE,
last_login BIGINT UNSIGNED NOT NULL DEFAULT 0,
permission_map BIGINT UNSIGNED NOT NULL DEFAULT 0
account_id INTEGER NOT NULL REFERENCES accounts(id),
name TEXT NOT NULL,
pending_name TEXT NOT NULL,
needs_rename INTEGER NOT NULL DEFAULT FALSE,
prop_clone_id BIGINT AUTO_INCREMENT UNIQUE,
last_login BIGINT NOT NULL DEFAULT 0,
permission_map BIGINT NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS charxml (
id BIGINT NOT NULL PRIMARY KEY REFERENCES charinfo(id),
xml_data LONGTEXT NOT NULL
xml_data TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS command_log (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
character_id BIGINT NOT NULL REFERENCES charinfo(id),
command VARCHAR(256) NOT NULL
command TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS friends (
player_id BIGINT NOT NULL REFERENCES charinfo(id),
friend_id BIGINT NOT NULL REFERENCES charinfo(id),
best_friend BOOLEAN NOT NULL DEFAULT FALSE,
best_friend INTEGER NOT NULL DEFAULT FALSE,
PRIMARY KEY (player_id, friend_id)
);
CREATE TABLE IF NOT EXISTS leaderboard (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
game_id INT UNSIGNED NOT NULL DEFAULT 0,
last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
game_id INTEGER NOT NULL DEFAULT 0,
last_played DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
character_id BIGINT NOT NULL REFERENCES charinfo(id),
time BIGINT UNSIGNED NOT NULL,
score BIGINT UNSIGNED NOT NULL DEFAULT 0
time BIGINT NOT NULL,
score BIGINT NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS mail (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
sender_id INT NOT NULL DEFAULT 0,
sender_name VARCHAR(35) NOT NULL DEFAULT '',
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
sender_id INTEGER NOT NULL DEFAULT 0,
sender_name TEXT NOT NULL DEFAULT '',
receiver_id BIGINT NOT NULL REFERENCES charinfo(id),
receiver_name VARCHAR(35) NOT NULL,
time_sent BIGINT UNSIGNED NOT NULL,
receiver_name TEXT NOT NULL,
time_sent BIGINT NOT NULL,
subject TEXT NOT NULL,
body TEXT NOT NULL,
attachment_id BIGINT NOT NULL DEFAULT 0,
attachment_lot INT NOT NULL DEFAULT 0,
attachment_lot INTEGER NOT NULL DEFAULT 0,
attachment_subkey BIGINT NOT NULL DEFAULT 0,
attachment_count INT NOT NULL DEFAULT 0,
was_read BOOLEAN NOT NULL DEFAULT FALSE
attachment_count INTEGER NOT NULL DEFAULT 0,
was_read INTEGER NOT NULL DEFAULT FALSE
);
CREATE TABLE IF NOT EXISTS object_id_tracker (
last_object_id BIGINT UNSIGNED NOT NULL DEFAULT 0 PRIMARY KEY
last_object_id BIGINT NOT NULL DEFAULT 0 PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS pet_names (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
pet_name TEXT NOT NULL,
approved INT UNSIGNED NOT NULL
approved INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS play_keys (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
key_string CHAR(19) NOT NULL UNIQUE,
key_uses INT NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
active BOOLEAN NOT NULL DEFAULT TRUE
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
key_string TEXT NOT NULL UNIQUE,
key_uses INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
active INTEGER NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS properties (
id BIGINT NOT NULL PRIMARY KEY,
owner_id BIGINT NOT NULL REFERENCES charinfo(id),
template_id INT UNSIGNED NOT NULL,
clone_id BIGINT UNSIGNED REFERENCES charinfo(prop_clone_id),
template_id INTEGER NOT NULL,
clone_id BIGINT REFERENCES charinfo(prop_clone_id),
name TEXT NOT NULL,
description TEXT NOT NULL,
rent_amount INT NOT NULL,
rent_amount INTEGER NOT NULL,
rent_due BIGINT NOT NULL,
privacy_option INT NOT NULL,
mod_approved BOOLEAN NOT NULL DEFAULT FALSE,
privacy_option INTEGER NOT NULL,
mod_approved INTEGER NOT NULL DEFAULT FALSE,
last_updated BIGINT NOT NULL,
time_claimed BIGINT NOT NULL,
rejection_reason TEXT NOT NULL,
reputation BIGINT UNSIGNED NOT NULL,
zone_id INT NOT NULL
reputation BIGINT NOT NULL,
zone_id INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS ugc (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
account_id INT NOT NULL REFERENCES accounts(id),
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
account_id INTEGER NOT NULL REFERENCES accounts(id),
character_id BIGINT NOT NULL REFERENCES charinfo(id),
is_optimized BOOLEAN NOT NULL DEFAULT FALSE,
lxfml MEDIUMBLOB NOT NULL,
bake_ao BOOLEAN NOT NULL DEFAULT FALSE,
is_optimized INTEGER NOT NULL DEFAULT FALSE,
lxfml BLOB NOT NULL,
bake_ao INTEGER NOT NULL DEFAULT FALSE,
filename TEXT NOT NULL DEFAULT ('')
);
CREATE TABLE IF NOT EXISTS properties_contents (
id BIGINT NOT NULL PRIMARY KEY,
property_id BIGINT NOT NULL REFERENCES properties(id),
ugc_id INT NULL REFERENCES ugc(id),
lot INT NOT NULL,
x FLOAT NOT NULL,
y FLOAT NOT NULL,
z FLOAT NOT NULL,
rx FLOAT NOT NULL,
ry FLOAT NOT NULL,
rz FLOAT NOT NULL,
rw FLOAT NOT NULL
ugc_id INTEGER NULL REFERENCES ugc(id),
lot INTEGER NOT NULL,
x DOUBLE NOT NULL,
y DOUBLE NOT NULL,
z DOUBLE NOT NULL,
rx DOUBLE NOT NULL,
ry DOUBLE NOT NULL,
rz DOUBLE NOT NULL,
rw DOUBLE NOT NULL
);
CREATE TABLE IF NOT EXISTS activity_log (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
character_id BIGINT NOT NULL REFERENCES charinfo(id),
activity INT NOT NULL,
time BIGINT UNSIGNED NOT NULL,
map_id INT NOT NULL
activity INTEGER NOT NULL,
time BIGINT NOT NULL,
map_id INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS bug_reports (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
body TEXT NOT NULL,
client_version TEXT NOT NULL,
other_player_id TEXT NOT NULL,
selection TEXT NOT NULL,
submitted TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP()
submitted DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP()
);
CREATE TABLE IF NOT EXISTS servers (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
name TEXT NOT NULL,
ip TEXT NOT NULL,
port INT NOT NULL,
state INT NOT NULL,
version INT NOT NULL DEFAULT 0
port INTEGER NOT NULL,
state INTEGER NOT NULL,
version INTEGER NOT NULL DEFAULT 0
);

View File

@@ -1,8 +1,8 @@
CREATE TABLE IF NOT EXISTS player_cheat_detections (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
account_id INT REFERENCES accounts(id),
account_id INTEGER REFERENCES accounts(id),
name TEXT REFERENCES charinfo(name),
violation_msg TEXT NOT NULL,
violation_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
violation_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
violation_system_address TEXT NOT NULL
);

View File

@@ -1,9 +1,9 @@
DROP TABLE IF EXISTS `player_cheat_detections`;
CREATE TABLE IF NOT EXISTS player_cheat_detections (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
account_id INT REFERENCES accounts(id),
account_id INTEGER REFERENCES accounts(id),
name TEXT NOT NULL,
violation_msg TEXT NOT NULL,
violation_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
violation_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(),
violation_system_address TEXT NOT NULL
);

View File

@@ -1,5 +1,5 @@
CREATE TABLE IF NOT EXISTS accounts_rewardcodes (
account_id INT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
rewardcode INT NOT NULL,
account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
rewardcode INTEGER NOT NULL,
PRIMARY KEY (account_id, rewardcode)
);

View File

@@ -1 +1 @@
ALTER TABLE behaviors MODIFY behavior_info LONGTEXT DEFAULT NULL;
ALTER TABLE behaviors MODIFY behavior_info TEXT DEFAULT NULL;

View File

@@ -1 +1 @@
ALTER TABLE bug_reports ADD reporter_id INT NOT NULL DEFAULT 0;
ALTER TABLE bug_reports ADD reporter_id INTEGER NOT NULL DEFAULT 0;

View File

@@ -1 +1 @@
ALTER TABLE properties ADD COLUMN performance_cost DOUBLE(20, 15) DEFAULT 0.0;
ALTER TABLE properties ADD COLUMN performance_cost DOUBLE DEFAULT 0.0;

View File

@@ -1,11 +1,11 @@
ALTER TABLE properties_contents
ADD COLUMN model_name TEXT NOT NULL DEFAULT "",
ADD COLUMN model_description TEXT NOT NULL DEFAULT "",
ADD COLUMN behavior_1 INT NOT NULL DEFAULT 0,
ADD COLUMN behavior_2 INT NOT NULL DEFAULT 0,
ADD COLUMN behavior_3 INT NOT NULL DEFAULT 0,
ADD COLUMN behavior_4 INT NOT NULL DEFAULT 0,
ADD COLUMN behavior_5 INT NOT NULL DEFAULT 0;
ADD COLUMN behavior_1 INTEGER NOT NULL DEFAULT 0,
ADD COLUMN behavior_2 INTEGER NOT NULL DEFAULT 0,
ADD COLUMN behavior_3 INTEGER NOT NULL DEFAULT 0,
ADD COLUMN behavior_4 INTEGER NOT NULL DEFAULT 0,
ADD COLUMN behavior_5 INTEGER NOT NULL DEFAULT 0;
UPDATE properties_contents SET model_name = CONCAT("Objects_", lot, "_name") WHERE model_name = "";
CREATE TABLE IF NOT EXISTS behaviors (id INT NOT NULL, behavior_info TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS behaviors (id INTEGER NOT NULL, behavior_info TEXT NOT NULL);

View File

@@ -1 +1 @@
ALTER TABLE accounts MODIFY play_key_id INT DEFAULT 0;
ALTER TABLE accounts MODIFY play_key_id INTEGER DEFAULT 0;

View File

@@ -1 +1 @@
ALTER TABLE accounts MODIFY play_key_id INT DEFAULT NULL;
ALTER TABLE accounts MODIFY play_key_id INTEGER DEFAULT NULL;

View File

@@ -1,12 +1,12 @@
ALTER TABLE leaderboard
ADD COLUMN tertiaryScore FLOAT NOT NULL DEFAULT 0,
ADD COLUMN numWins INT NOT NULL DEFAULT 0,
ADD COLUMN timesPlayed INT NOT NULL DEFAULT 1,
MODIFY time INT NOT NULL DEFAULT 0;
ADD COLUMN tertiaryScore DOUBLE NOT NULL DEFAULT 0,
ADD COLUMN numWins INTEGER NOT NULL DEFAULT 0,
ADD COLUMN timesPlayed INTEGER NOT NULL DEFAULT 1,
MODIFY time INTEGER NOT NULL DEFAULT 0;
/* Can only ALTER one column at a time... */
ALTER TABLE leaderboard CHANGE score primaryScore FLOAT NOT NULL DEFAULT 0;
ALTER TABLE leaderboard CHANGE time secondaryScore FLOAT NOT NULL DEFAULT 0 AFTER primaryScore;
ALTER TABLE leaderboard CHANGE score primaryScore DOUBLE NOT NULL DEFAULT 0;
ALTER TABLE leaderboard CHANGE time secondaryScore DOUBLE NOT NULL DEFAULT 0 AFTER primaryScore;
/* A bit messy, but better than going through a bunch of code fixes all to be run once. */
UPDATE leaderboard SET
@@ -15,4 +15,4 @@ UPDATE leaderboard SET
/* Do this last so we dont update entry times erroneously */
ALTER TABLE leaderboard
CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP();
CHANGE last_played last_played DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP();

View File

@@ -4,6 +4,15 @@ enable_testing()
find_package(GoogleTest REQUIRED)
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_subdirectory(dCommonTests)
add_subdirectory(dGameTests)

View File

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

View File

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

143161
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