aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-07-13 13:16:38 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-07-13 13:16:38 -0300
commitc0f9ee43affe07b93b553c0af9e705dd88bfe772 (patch)
treec3cc2a180d22c63642cb0d088696813df9065e48
parentf827fcc84815c3727aae652d2a60a801b2432768 (diff)
downloadquerymanager-c0f9ee43affe07b93b553c0af9e705dd88bfe772.tar.gz
querymanager-c0f9ee43affe07b93b553c0af9e705dd88bfe772.zip
impl BANISH_ACCOUNT
-rw-r--r--sql/schema.sql3
-rw-r--r--src/connections.cc111
-rw-r--r--src/database.cc92
-rw-r--r--src/querymanager.hh50
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);