diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-07-13 13:16:38 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-07-13 13:16:38 -0300 |
| commit | c0f9ee43affe07b93b553c0af9e705dd88bfe772 (patch) | |
| tree | c3cc2a180d22c63642cb0d088696813df9065e48 | |
| parent | f827fcc84815c3727aae652d2a60a801b2432768 (diff) | |
| download | querymanager-c0f9ee43affe07b93b553c0af9e705dd88bfe772.tar.gz querymanager-c0f9ee43affe07b93b553c0af9e705dd88bfe772.zip | |
impl BANISH_ACCOUNT
| -rw-r--r-- | sql/schema.sql | 3 | ||||
| -rw-r--r-- | src/connections.cc | 111 | ||||
| -rw-r--r-- | src/database.cc | 92 | ||||
| -rw-r--r-- | src/querymanager.hh | 50 |
4 files changed, 216 insertions, 40 deletions
diff --git a/sql/schema.sql b/sql/schema.sql index f027e35..56c36fd 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -142,9 +142,12 @@ CREATE TABLE IF NOT EXISTS Banishments ( Reason TEXT NOT NULL, Comment TEXT NOT NULL, FinalWarning INTEGER NOT NULL, + Issued INTEGER NOT NULL, Until INTEGER NOT NULL, PRIMARY KEY (BanishmentID) ); +CREATE INDEX IF NOT EXISTS BanishmentsAccount + ON Banishments(AccountID, Until, FinalWarning); CREATE TABLE IF NOT EXISTS IPBanishments ( IPAddress INTEGER NOT NULL, diff --git a/src/connections.cc b/src/connections.cc index 52c4fea..3672eaa 100644 --- a/src/connections.cc +++ b/src/connections.cc @@ -534,8 +534,9 @@ void ProcessSetNamelockQuery(TConnection *Connection, TReadBuffer *Buffer){ Buffer->ReadString(Reason, sizeof(Reason)); Buffer->ReadString(Comment, sizeof(Comment)); - int IPAddress; - if(!ParseIPAddress(IPString, &IPAddress)){ + // TODO(fusion): Might not even allow empty ip string. + int IPAddress = 0; + if(IPString[0] != 0 && !ParseIPAddress(IPString, &IPAddress)){ SendQueryStatusFailed(Connection); return; } @@ -551,15 +552,16 @@ void ProcessSetNamelockQuery(TConnection *Connection, TReadBuffer *Buffer){ SendQueryStatusError(Connection, 1); return; } - + + // TODO(fusion): Might be `NO_BANISHMENT`. if(GetCharacterRight(CharacterID, "NAMELOCK")){ SendQueryStatusError(Connection, 2); return; } - bool Approved; - if(GetNamelockStatus(CharacterID, &Approved)){ - SendQueryStatusError(Connection, (Approved ? 4 : 3)); + TNamelockStatus Status = GetNamelockStatus(CharacterID); + if(Status.Namelocked){ + SendQueryStatusError(Connection, (Status.Approved ? 4 : 3)); return; } @@ -577,7 +579,80 @@ void ProcessSetNamelockQuery(TConnection *Connection, TReadBuffer *Buffer){ } void ProcessBanishAccountQuery(TConnection *Connection, TReadBuffer *Buffer){ - SendQueryStatusFailed(Connection); + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + char CharacterName[30]; + char IPString[16]; + char Reason[200]; + char Comment[200]; + int GamemasterID = Buffer->Read16(); + Buffer->ReadString(CharacterName, sizeof(CharacterName)); + Buffer->ReadString(IPString, sizeof(IPString)); + Buffer->ReadString(Reason, sizeof(Reason)); + Buffer->ReadString(Comment, sizeof(Comment)); + bool FinalWarning = Buffer->ReadFlag(); + + // TODO(fusion): Might not even allow empty ip string. + int IPAddress = 0; + if(IPString[0] != 0 && !ParseIPAddress(IPString, &IPAddress)){ + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("BanishAccount"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); + if(CharacterID == 0){ + SendQueryStatusError(Connection, 1); + return; + } + + // TODO(fusion): Might be `NO_BANISHMENT`. + if(GetCharacterRight(CharacterID, "BANISHMENT")){ + SendQueryStatusError(Connection, 2); + return; + } + + TBanishmentStatus Status = GetBanishmentStatus(CharacterID); + if(Status.Banished){ + SendQueryStatusError(Connection, 3); + return; + } + + // TODO(fusion): We might want to add all these constants as config values. + int BanishmentID; + int Days = 7; + if(Status.FinalWarning){ + Days = 0; // permanent + FinalWarning = false; + }else if(Status.TimesBanished >= 5 || FinalWarning){ + Days = 30; + FinalWarning = true; + } + + if(!InsertBanishment(CharacterID, IPAddress, GamemasterID, + Reason, Comment, FinalWarning, Days * 86400, &BanishmentID)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + WriteBuffer.Write32((uint32)BanishmentID); + WriteBuffer.Write8(Days > 0 ? Days : 0xFF); + WriteBuffer.WriteFlag(FinalWarning); + SendResponse(Connection, &WriteBuffer); } void ProcessSetNotationQuery(TConnection *Connection, TReadBuffer *Buffer){ @@ -1005,20 +1080,28 @@ void ProcessExcludeFromAuctionsQuery(TConnection *Connection, TReadBuffer *Buffe int CharacterID = (int)Buffer->Read32(); bool Banish = Buffer->ReadFlag(); - int Duration = 7 * 86400; // one week + int ExclusionDays = 7; int BanishmentID = 0; if(Banish){ - Duration = 30 * 86400; // one month -#if 0 - // TODO(fusion): When banishment is implemented. - if(!BanishCharacterAccount(CharacterID, &BanishmentID, ..., "Spoiling Auction")){ + bool FinalWarning = false; + int BanishmentDays = 7; + TBanishmentStatus Status = GetBanishmentStatus(CharacterID); + if(Status.FinalWarning){ + BanishmentDays = 0; // permanent + }else if(Status.TimesBanished >= 5){ + BanishmentDays = 30; + FinalWarning = true; + } + + if(!InsertBanishment(CharacterID, 0, 0, "Spoiling Auction", + "", FinalWarning, BanishmentDays * 86400, &BanishmentID)){ SendQueryStatusFailed(Connection); return; } -#endif } - if(!ExcludeFromAuctions(Connection->WorldID, CharacterID, Duration, BanishmentID)){ + if(!ExcludeFromAuctions(Connection->WorldID, + CharacterID, ExclusionDays * 86400, BanishmentID)){ SendQueryStatusFailed(Connection); return; } diff --git a/src/database.cc b/src/database.cc index 55bec11..5d48bac 100644 --- a/src/database.cc +++ b/src/database.cc @@ -760,31 +760,31 @@ bool ExcludeFromAuctions(int WorldID, int CharacterID, int Duration, int Banishm // Banishment tables //============================================================================== -bool GetNamelockStatus(int CharacterID, bool *Approved){ - ASSERT(Approved); +TNamelockStatus GetNamelockStatus(int CharacterID){ + TNamelockStatus Status = {}; sqlite3_stmt *Stmt = PrepareQuery( "SELECT Approved FROM Namelocks WHERE CharacterID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); - return false; + return Status; } if(sqlite3_bind_int(Stmt, 1, CharacterID) != SQLITE_OK){ LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); - return false; + return Status; } 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; + return Status; } - bool Result = (ErrorCode == SQLITE_ROW); - if(Result){ - *Approved = (sqlite3_column_int(Stmt, 0) != 0); + Status.Namelocked = (ErrorCode == SQLITE_ROW); + if(Status.Namelocked){ + Status.Approved = (sqlite3_column_int(Stmt, 0) != 0); } - return Result; + return Status; } bool InsertNamelock(int CharacterID, int IPAddress, @@ -815,6 +815,80 @@ bool InsertNamelock(int CharacterID, int IPAddress, return true; } +TBanishmentStatus GetBanishmentStatus(int CharacterID){ + TBanishmentStatus Status = {}; + sqlite3_stmt *Stmt = PrepareQuery( + "SELECT B.FinalWarning, (B.Until = B.Issued OR B.Until > UNIXEPOCH())" + " FROM Banishments AS B" + " LEFT JOIN Characters AS C ON C.AccountID = B.AccountID" + " WHERE C.CharacterID = ?1"); + if(Stmt == NULL){ + LOG_ERR("Failed to prepare query"); + return Status; + } + + if(sqlite3_bind_int(Stmt, 1, CharacterID) != SQLITE_OK){ + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + return Status; + } + + while(sqlite3_step(Stmt) == SQLITE_ROW){ + Status.TimesBanished += 1; + + if(sqlite3_column_int(Stmt, 0) != 0){ + Status.FinalWarning = true; + } + + if(sqlite3_column_int(Stmt, 1) != 0){ + Status.Banished = true; + } + } + + if(sqlite3_errcode(g_Database) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + return Status; + } + + return Status; +} + +bool InsertBanishment(int CharacterID, int IPAddress, + int GamemasterID, const char *Reason, const char *Comment, + bool FinalWarning, int Duration, int *BanishmentID){ + // TODO(fusion): Not sure if I like these insertions with subqueries. We might + // as well just get the account id before hand. + ASSERT(BanishmentID); + sqlite3_stmt *Stmt = PrepareQuery( + "INSERT INTO Banishments (AccountID, IPAddress, GamemasterID," + " Reason, Comment, FinalWarning, Issued, Until)" + " VALUES ((SELECT AccountID FROM Characters WHERE CharacterID = ?1)," + " ?2, ?3, ?4, ?5, ?6, UNIXEPOCH(), UNIXEPOCH() + ?7)" + " RETURNING BanishmentID"); + if(Stmt == NULL){ + LOG_ERR("Failed to prepare query"); + return false; + } + + if(sqlite3_bind_int(Stmt, 1, CharacterID) != SQLITE_OK + || sqlite3_bind_int(Stmt, 2, IPAddress) != SQLITE_OK + || sqlite3_bind_int(Stmt, 3, GamemasterID) != SQLITE_OK + || sqlite3_bind_text(Stmt, 4, Reason, -1, NULL) != SQLITE_OK + || sqlite3_bind_text(Stmt, 5, Comment, -1, NULL) != SQLITE_OK + || sqlite3_bind_int(Stmt, 6, FinalWarning) != SQLITE_OK + || sqlite3_bind_int(Stmt, 7, Duration) != SQLITE_OK){ + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + return false; + } + + if(sqlite3_step(Stmt) != SQLITE_ROW){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + return false; + } + + *BanishmentID = sqlite3_column_int(Stmt, 0); + return true; +} + // Info tables //============================================================================== bool DeleteOnlineCharacters(int WorldID){ diff --git a/src/querymanager.hh b/src/querymanager.hh index 40066af..9e54ea8 100644 --- a/src/querymanager.hh +++ b/src/querymanager.hh @@ -647,6 +647,22 @@ void ProcessConnectionQuery(TConnection *Connection); // database.cc //============================================================================== +struct TWorldConfig{ + int Type; + int RebootTime; + int Address; + int Port; + int MaxPlayers; + int PremiumPlayerBuffer; + int MaxNewbies; + int PremiumNewbieBuffer; +}; + +struct TCharacterIndexEntry{ + char Name[30]; + int CharacterID; +}; + struct THouseAuction{ int HouseID; int BidderID; @@ -687,28 +703,24 @@ struct THouse{ bool GuildHouse; }; -struct TOnlineCharacter{ - char Name[30]; - int Level; - char Profession[30]; +struct TNamelockStatus{ + bool Namelocked; + bool Approved; }; -struct TCharacterIndexEntry{ - char Name[30]; - int CharacterID; +struct TBanishmentStatus{ + bool Banished; + bool FinalWarning; + int TimesBanished; }; -struct TWorldConfig{ - int Type; - int RebootTime; - int Address; - int Port; - int MaxPlayers; - int PremiumPlayerBuffer; - int MaxNewbies; - int PremiumNewbieBuffer; +struct TOnlineCharacter{ + char Name[30]; + int Level; + char Profession[30]; }; +// NOTE(fusion): Transaction scope guard. struct TransactionScope{ private: const char *m_Context; @@ -748,9 +760,13 @@ bool InsertHouses(int WorldID, int NumHouses, THouse *Houses); bool ExcludeFromAuctions(int WorldID, int CharacterID, int Duration, int BanishmentID); // NOTE(fusion): Banishment tables. -bool GetNamelockStatus(int CharacterID, bool *Approved); +TNamelockStatus GetNamelockStatus(int CharacterID); bool InsertNamelock(int CharacterID, int IPAddress, int GamemasterID, const char *Reason, const char *Comment); +TBanishmentStatus GetBanishmentStatus(int CharacterID); +bool InsertBanishment(int CharacterID, int IPAddress, + int GamemasterID, const char *Reason, const char *Comment, + bool FinalWarning, int Duration, int *BanishmentID); // NOTE(fusion): Info tables. bool DeleteOnlineCharacters(int WorldID); |
