aboutsummaryrefslogtreecommitdiff
path: root/src/database.cc
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-07-12 20:44:10 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-07-12 20:44:10 -0300
commit1cad8468cbaf61309a64194fca547a67a700a6da (patch)
tree603ed4e52f28ec502bd2c820a133b42022cd8f37 /src/database.cc
parentac41dc912dbdee28a01a4db0404bdb363d5530dc (diff)
downloadquerymanager-1cad8468cbaf61309a64194fca547a67a700a6da.tar.gz
querymanager-1cad8468cbaf61309a64194fca547a67a700a6da.zip
house related queries
Diffstat (limited to 'src/database.cc')
-rw-r--r--src/database.cc390
1 files changed, 364 insertions, 26 deletions
diff --git a/src/database.cc b/src/database.cc
index 1064547..4311fa2 100644
--- a/src/database.cc
+++ b/src/database.cc
@@ -144,7 +144,7 @@ bool TransactionScope::Commit(void){
// Queries
//==============================================================================
bool LoadWorldID(const char *WorldName, int *WorldID){
- ASSERT(WorldName && WorldID);
+ ASSERT(WorldName != NULL && WorldID != NULL);
sqlite3_stmt *Stmt = PrepareQuery(
"SELECT WorldID FROM Worlds WHERE Name = ?1");
if(Stmt == NULL){
@@ -166,8 +166,283 @@ bool LoadWorldID(const char *WorldName, int *WorldID){
return true;
}
-bool LoadHouseOwners(int WorldID, DynamicArray<THouseOwner> *HouseOwners){
- ASSERT(HouseOwners);
+bool DecrementIsOnline(int WorldID, int CharacterID){
+ // NOTE(fusion): A character is uniquely identified by its id. The world id
+ // check is purely to avoid a world from modifying a character from another
+ // world.
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "UPDATE Characters SET IsOnline = IsOnline - 1"
+ " WHERE WorldID = ?1 AND CharacterID = ?2");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 2, CharacterID) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return sqlite3_changes(g_Database) > 0;
+}
+
+bool FinishHouseAuctions(int WorldID, DynamicArray<THouseAuction> *Auctions){
+ ASSERT(Auctions != NULL);
+ // TODO(fusion): If the application crashes while processing finished auctions,
+ // non processed auctions will be lost but with no other side-effects. It could
+ // be an inconvenience but it's not a big problem.
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "DELETE FROM HouseAuctions"
+ " WHERE WorldID = ?1 AND FinishTime != NULL AND FinishTime <= UNIXEPOCH()"
+ " RETURNING HouseID, BidderID, BidAmount, FinishTime,"
+ " (SELECT Name FROM Characters WHERE CharacterID = BidderID)");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){
+ LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ while(sqlite3_step(Stmt) == SQLITE_ROW){
+ THouseAuction Auction = {};
+ Auction.HouseID = sqlite3_column_int(Stmt, 0);
+ Auction.BidderID = sqlite3_column_int(Stmt, 1);
+ Auction.BidAmount = sqlite3_column_int(Stmt, 2);
+ Auction.FinishTime = sqlite3_column_int(Stmt, 3);
+ StringCopy(Auction.BidderName, sizeof(Auction.BidderName),
+ (const char*)sqlite3_column_text(Stmt, 4));
+ Auctions->Push(Auction);
+ }
+
+ if(sqlite3_errcode(g_Database) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return true;
+}
+
+bool FinishHouseTransfers(int WorldID, DynamicArray<THouseTransfer> *Transfers){
+ ASSERT(Transfers != NULL);
+ // TODO(fusion): Same as `FinishHouseAuctions` but with house transfers.
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "DELETE FROM HouseTransfers"
+ " WHERE WorldID = ?1"
+ " RETURNING HouseID, NewOwnerID, Price,"
+ " (SELECT Name FROM Characters WHERE CharacterID = NewOwnerID)");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){
+ LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ while(sqlite3_step(Stmt) == SQLITE_ROW){
+ THouseTransfer Transfer = {};
+ Transfer.HouseID = sqlite3_column_int(Stmt, 0);
+ Transfer.NewOwnerID = sqlite3_column_int(Stmt, 1);
+ Transfer.Price = sqlite3_column_int(Stmt, 2);
+ StringCopy(Transfer.NewOwnerName, sizeof(Transfer.NewOwnerName),
+ (const char*)sqlite3_column_text(Stmt, 4));
+ Transfers->Push(Transfer);
+ }
+
+ if(sqlite3_errcode(g_Database) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return true;
+}
+
+bool LoadFreeAccountEvictions(int WorldID, DynamicArray<THouseEviction> *Evictions){
+ ASSERT(Evictions != NULL);
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "SELECT O.HouseID, O.OwnerID"
+ " FROM HouseOwners AS O"
+ " LEFT JOIN CharacterRights AS R"
+ " ON R.CharacterID = O.OwnerID AND R.Right = 'PREMIUM_ACCOUNT'"
+ " WHERE O.WorldID = ?1 AND R.CharacterID IS NULL");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){
+ LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ while(sqlite3_step(Stmt) == SQLITE_ROW){
+ THouseEviction Eviction = {};
+ Eviction.HouseID = sqlite3_column_int(Stmt, 0);
+ Eviction.OwnerID = sqlite3_column_int(Stmt, 1);
+ Evictions->Push(Eviction);
+ }
+
+ if(sqlite3_errcode(g_Database) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return true;
+}
+
+bool LoadDeletedCharacterEvictions(int WorldID, DynamicArray<THouseEviction> *Evictions){
+ ASSERT(Evictions != NULL);
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "SELECT O.HouseID, O.OwnerID"
+ " FROM HouseOwners AS O"
+ " LEFT JOIN Characters AS C ON C.CharacterID = O.OwnerID"
+ " WHERE O.WorldID = ?1"
+ " AND (C.CharacterID IS NULL OR C.Deleted != 0)");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){
+ LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ while(sqlite3_step(Stmt) == SQLITE_ROW){
+ THouseEviction Eviction = {};
+ Eviction.HouseID = sqlite3_column_int(Stmt, 0);
+ Eviction.OwnerID = sqlite3_column_int(Stmt, 1);
+ Evictions->Push(Eviction);
+ }
+
+ if(sqlite3_errcode(g_Database) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return true;
+}
+
+bool CheckGuildLeaderStatus(int WorldID, int CharacterID, bool *IsGuildLeader){
+ // NOTE(fusion): Same as `DecrementIsOnline`.
+ ASSERT(IsGuildLeader != NULL);
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "SELECT Guild, Rank FROM Characters"
+ " WHERE WorldID = ?1 AND CharacterID = ?2");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 2, CharacterID) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ int ErrorCode = sqlite3_step(Stmt);
+ if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ *IsGuildLeader = false;
+ if(ErrorCode == SQLITE_ROW){
+ const char *Guild = (const char*)sqlite3_column_text(Stmt, 0);
+ const char *Rank = (const char*)sqlite3_column_text(Stmt, 1);
+ if(Guild != NULL && Rank != NULL && Guild[0] != 0 && StringEqCI(Rank, "Leader")){
+ *IsGuildLeader = true;
+ }
+ }
+
+ return true;
+}
+
+bool InsertHouseOwner(int WorldID, int HouseID, int OwnerID, int PaidUntil){
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "INSERT INTO HouseOwners (WorldID, HouseID, OwnerID, PaidUntil)"
+ " VALUES (?1, ?2, ?3, ?4)");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 2, HouseID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 3, OwnerID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 4, PaidUntil) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return true;
+}
+
+bool UpdateHouseOwner(int WorldID, int HouseID, int OwnerID, int PaidUntil){
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "UPDATE HouseOwners SET OwnerID = ?3, PaidUntil = ?4"
+ " WHERE WorldID = ?1 AND HouseID = ?2");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 2, HouseID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 3, OwnerID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 4, PaidUntil) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return sqlite3_changes(g_Database) > 0;
+}
+
+bool DeleteHouseOwner(int WorldID, int HouseID){
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "DELETE FROM HouseOwners"
+ " WHERE WorldID = ?1 AND HouseID = ?2");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 2, HouseID) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return sqlite3_changes(g_Database) > 0;
+}
+
+bool LoadHouseOwners(int WorldID, DynamicArray<THouseOwner> *Owners){
+ ASSERT(Owners != NULL);
sqlite3_stmt *Stmt = PrepareQuery(
"SELECT O.HouseID, O.OwnerID, C.Name, O.PaidUntil"
" FROM HouseOwners AS O"
@@ -190,7 +465,33 @@ bool LoadHouseOwners(int WorldID, DynamicArray<THouseOwner> *HouseOwners){
StringCopy(Owner.OwnerName, sizeof(Owner.OwnerName),
(const char*)sqlite3_column_text(Stmt, 2));
Owner.PaidUntil = sqlite3_column_int(Stmt, 3);
- HouseOwners->Push(Owner);
+ Owners->Push(Owner);
+ }
+
+ if(sqlite3_errcode(g_Database) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return true;
+}
+
+bool LoadHouseAuctions(int WorldID, DynamicArray<int> *Auctions){
+ ASSERT(Auctions != NULL);
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "SELECT HouseID FROM HouseAuctions WHERE WorldID = ?1");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){
+ LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ while(sqlite3_step(Stmt) == SQLITE_ROW){
+ Auctions->Push(sqlite3_column_int(Stmt, 0));
}
if(sqlite3_errcode(g_Database) != SQLITE_DONE){
@@ -201,6 +502,28 @@ bool LoadHouseOwners(int WorldID, DynamicArray<THouseOwner> *HouseOwners){
return true;
}
+bool StartHouseAuction(int WorldID, int HouseID){
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "INSERT INTO HouseAuctions (WorldID, HouseID) VALUES (?1, ?2)");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 2, HouseID) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return true;
+}
+
bool DeleteHouses(int WorldID){
sqlite3_stmt *Stmt = PrepareQuery(
"DELETE FROM Houses WHERE WorldID = ?1");
@@ -345,7 +668,7 @@ bool InsertOnlineCharacters(int WorldID, int NumCharacters, TOnlineCharacter *Ch
}
bool CheckOnlineRecord(int WorldID, int NumCharacters, bool *NewRecord){
- ASSERT(NewRecord);
+ ASSERT(NewRecord != NULL);
sqlite3_stmt *Stmt = PrepareQuery(
"UPDATE Worlds SET OnlineRecord = ?2"
" WHERE WorldID = ?1 AND OnlineRecord < ?2");
@@ -354,13 +677,9 @@ bool CheckOnlineRecord(int WorldID, int NumCharacters, bool *NewRecord){
return false;
}
- if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){
- LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database));
- return false;
- }
-
- if(sqlite3_bind_int(Stmt, 2, NumCharacters) != SQLITE_OK){
- LOG_ERR("Failed to bind NumCharacters: %s", sqlite3_errmsg(g_Database));
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 2, NumCharacters) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database));
return false;
}
@@ -375,7 +694,7 @@ bool CheckOnlineRecord(int WorldID, int NumCharacters, bool *NewRecord){
bool LoadCharacterIndex(int WorldID, int MinimumCharacterID,
int MaxEntries, int *NumEntries, TCharacterIndexEntry *Entries){
- ASSERT(MaxEntries > 0 && NumEntries && Entries);
+ ASSERT(MaxEntries > 0 && NumEntries != NULL && Entries != NULL);
sqlite3_stmt *Stmt = PrepareQuery(
"SELECT CharacterID, Name FROM Characters"
" WHERE WorldID = ?1 AND CharacterID >= ?2"
@@ -385,18 +704,10 @@ bool LoadCharacterIndex(int WorldID, int MinimumCharacterID,
return false;
}
- if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){
- LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database));
- return false;
- }
-
- if(sqlite3_bind_int(Stmt, 2, MinimumCharacterID) != SQLITE_OK){
- LOG_ERR("Failed to bind MinimumCharacterID: %s", sqlite3_errmsg(g_Database));
- return false;
- }
-
- if(sqlite3_bind_int(Stmt, 3, MaxEntries) != SQLITE_OK){
- LOG_ERR("Failed to bind MaxEntries: %s", sqlite3_errmsg(g_Database));
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 2, MinimumCharacterID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 3, MaxEntries) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database));
return false;
}
@@ -419,8 +730,35 @@ bool LoadCharacterIndex(int WorldID, int MinimumCharacterID,
return true;
}
+bool ExcludeFromAuctions(int WorldID, int CharacterID, int Duration, int BanishmentID){
+ // NOTE(fusion): Same as `DecrementIsOnline`.
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "INSERT INTO HouseAuctionExclusions (CharacterID, Until, BanishmentID)"
+ " SELECT CharacterID, (UNIXEPOCH() + ?3), ?4 FROM Characters"
+ " WHERE WorldID = ?1 AND CharacterID = ?2");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 2, CharacterID) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 3, Duration) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 3, BanishmentID) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return sqlite3_changes(g_Database) > 0;
+}
+
bool LoadWorldConfig(int WorldID, TWorldConfig *WorldConfig){
- ASSERT(WorldConfig);
+ ASSERT(WorldConfig != NULL);
sqlite3_stmt *Stmt = PrepareQuery(
"SELECT Type, RebootTime, Address, Port, MaxPlayers,"
" PremiumPlayerBuffer, MaxNewbies, PremiumNewbieBuffer"