aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--sql/schema.sql42
-rw-r--r--src/connections.cc147
-rw-r--r--src/database.cc266
-rw-r--r--src/querymanager.hh64
5 files changed, 470 insertions, 51 deletions
diff --git a/Makefile b/Makefile
index ca7ce70..de46d42 100644
--- a/Makefile
+++ b/Makefile
@@ -11,10 +11,8 @@ LFLAGS = -Wl,-t
DEBUG ?= 0
ifneq ($(DEBUG), 0)
CFLAGS += -g -O0
- CXXFLAGS += -g -O0
else
CFLAGS += -O2
- CXXFLAGS += -O2
endif
$(BUILDDIR)/$(OUTPUTEXE): $(BUILDDIR)/connections.obj $(BUILDDIR)/database.obj $(BUILDDIR)/querymanager.obj $(BUILDDIR)/sqlite3.obj
diff --git a/sql/schema.sql b/sql/schema.sql
index 18c2938..6ec136b 100644
--- a/sql/schema.sql
+++ b/sql/schema.sql
@@ -1,3 +1,5 @@
+-- Primary Tables
+--==============================================================================
CREATE TABLE IF NOT EXISTS Worlds (
WorldID INTEGER NOT NULL,
Name TEXT NOT NULL COLLATE NOCASE,
@@ -9,6 +11,7 @@ CREATE TABLE IF NOT EXISTS Worlds (
PremiumPlayerBuffer INTEGER NOT NULL,
MaxNewbies INTEGER NOT NULL,
PremiumNewbieBuffer INTEGER NOT NULL,
+ OnlineRecord INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (WorldID),
UNIQUE (Name)
);
@@ -23,8 +26,6 @@ CREATE TABLE IF NOT EXISTS Accounts (
UNIQUE (EMail)
);
--- Character Tables
---==============================================================================
CREATE TABLE IF NOT EXISTS Characters (
WorldID INTEGER NOT NULL,
CharacterID INTEGER NOT NULL,
@@ -34,10 +35,12 @@ CREATE TABLE IF NOT EXISTS Characters (
Guild TEXT NOT NULL COLLATE NOCASE DEFAULT '',
Rank TEXT NOT NULL COLLATE NOCASE DEFAULT '',
Title TEXT NOT NULL COLLATE NOCASE DEFAULT '',
+ IsOnline INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (CharacterID),
UNIQUE (Name)
);
-CREATE INDEX IF NOT EXISTS CharactersAccountIndex ON Characters(AccountID);
+CREATE INDEX IF NOT EXISTS CharactersWorldIndex ON Characters(WorldID, IsOnline);
+CREATE INDEX IF NOT EXISTS CharactersAccountIndex ON Characters(AccountID, IsOnline);
CREATE INDEX IF NOT EXISTS CharactersGuildIndex ON Characters(Guild);
CREATE TABLE IF NOT EXISTS CharacterBuddies (
@@ -58,15 +61,6 @@ CREATE TABLE IF NOT EXISTS CharacterRights (
PRIMARY KEY(CharacterID, Right)
);
-CREATE TABLE IF NOT EXISTS OnlineCharacters (
- CharacterID INTEGER NOT NULL,
- AccountID INTEGER NOT NULL,
- IPAddress INTEGER NOT NULL,
- MultiClient INTEGER NOT NULL,
- PRIMARY KEY (CharacterID)
-);
-CREATE INDEX IF NOT EXISTS OnlineCharactersAccountIndex ON OnlineCharacters(AccountID);
-
-- House Tables
--==============================================================================
CREATE TABLE IF NOT EXISTS Houses (
@@ -175,3 +169,27 @@ CREATE TABLE IF NOT EXISTS Statements (
Comment TEXT NOT NULL,
PRIMARY KEY (Timestamp, StatementID)
);
+
+-- Info Tables
+--==============================================================================
+CREATE TABLE IF NOT EXISTS KillStatistics (
+ WorldID INTEGER NOT NULL,
+ Name TEXT NOT NULL COLLATE NOCASE,
+ TimesKilled INTEGER NOT NULL,
+ PlayersKilled INTEGER NOT NULL,
+ PRIMARY KEY (WorldID, Name)
+);
+
+CREATE TABLE IF NOT EXISTS OnlineCharacters (
+ WorldID INTEGER NOT NULL,
+ Name TEXT NOT NULL COLLATE NOCASE,
+ Level INTEGER NOT NULL,
+ Profession TEXT NOT NULL,
+ PRIMARY KEY (WorldID, Name)
+);
+
+-- REMOVE(fusion): Testing Data.
+--==============================================================================
+INSERT INTO Worlds (Name, Type, RebootTime, Address, Port, MaxPlayers,
+ PremiumPlayerBuffer, MaxNewbies, PremiumNewbieBuffer)
+ VALUES ("Zanera", 0, 5, 0x7F000001, 7172, 1000, 100, 300, 100);
diff --git a/src/connections.cc b/src/connections.cc
index c2dca03..b2b3daf 100644
--- a/src/connections.cc
+++ b/src/connections.cc
@@ -466,25 +466,38 @@ void SendQueryStatusFailed(TConnection *Connection){
void ProcessLoginQuery(TConnection *Connection, TReadBuffer *Buffer){
char Password[30];
- char ApplicationData[30];
+ char LoginData[30];
int ApplicationType = Buffer->Read8();
Buffer->ReadString(Password, sizeof(Password));
if(ApplicationType == APPLICATION_TYPE_GAME){
- Buffer->ReadString(ApplicationData, sizeof(ApplicationData));
+ Buffer->ReadString(LoginData, sizeof(LoginData));
}
+ // TODO(fusion): Probably just disconnect on failed login attempt? Implement
+ // write then disconnect?
if(!StringEq(g_Password, Password)){
LOG_WARN("Invalid login attempt from %s", Connection->RemoteAddress);
SendQueryStatusFailed(Connection);
return;
}
- LOG("Connection %s AUTHORIZED", Connection->RemoteAddress);
+ int WorldID = -1;
+ if(ApplicationType == APPLICATION_TYPE_GAME){
+ if(!LoadWorldID(LoginData, &WorldID)){
+ LOG_WARN("Invalid world name \"%s\"", LoginData);
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+
+ LOG("Connection %s AUTHORIZED to world \"%s\" (%d)",
+ Connection->RemoteAddress, LoginData, WorldID);
+ }else{
+ LOG("Connection %s AUTHORIZED", Connection->RemoteAddress);
+ }
+
Connection->Authorized = true;
Connection->ApplicationType = ApplicationType;
- StringCopy(Connection->ApplicationData,
- sizeof(Connection->ApplicationData),
- ApplicationData);
+ Connection->WorldID = WorldID;
SendQueryStatusOk(Connection);
}
@@ -579,8 +592,7 @@ void ProcessGetHouseOwnersQuery(TConnection *Connection, TReadBuffer *Buffer){
}
DynamicArray<THouseOwner> HouseOwners;
- const char *WorldName = Connection->ApplicationData;
- if(!LoadHouseOwners(WorldName, &HouseOwners)){
+ if(!LoadHouseOwners(Connection->WorldID, &HouseOwners)){
SendQueryStatusFailed(Connection);
return;
}
@@ -606,15 +618,101 @@ void ProcessStartAuctionQuery(TConnection *Connection, TReadBuffer *Buffer){
}
void ProcessInsertHousesQuery(TConnection *Connection, TReadBuffer *Buffer){
- SendQueryStatusFailed(Connection);
+ if(Connection->ApplicationType != APPLICATION_TYPE_GAME){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+
+ // TODO(fusion): Have some TransactionGuard type that will either commit or
+ // rollback a transaction when we leave its scope, based on whether some
+ // `Commit` function was called.
+
+ if(!DeleteHouses(Connection->WorldID)){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+
+ int NumHouses = Buffer->Read16();
+ if(NumHouses > 0){
+ THouse *Houses = (THouse*)alloca(NumHouses * sizeof(THouse));
+ for(int i = 0; i < NumHouses; i += 1){
+ Houses[i].HouseID = Buffer->Read16();
+ Buffer->ReadString(Houses[i].Name, sizeof(Houses[i].Name));
+ Houses[i].Rent = (int)Buffer->Read32();
+ Buffer->ReadString(Houses[i].Description, sizeof(Houses[i].Description));
+ Houses[i].Size = Buffer->Read16();
+ Houses[i].PositionX = Buffer->Read16();
+ Houses[i].PositionY = Buffer->Read16();
+ Houses[i].PositionZ = Buffer->Read8();
+ Buffer->ReadString(Houses[i].Town, sizeof(Houses[i].Town));
+ Houses[i].GuildHouse = Buffer->ReadFlag();
+ }
+
+ if(!InsertHouses(Connection->WorldID, NumHouses, Houses)){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+ }
+
+ SendQueryStatusOk(Connection);
}
void ProcessClearIsOnlineQuery(TConnection *Connection, TReadBuffer *Buffer){
- SendQueryStatusFailed(Connection);
+ if(Connection->ApplicationType != APPLICATION_TYPE_GAME){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+
+ int NumAffectedCharacters;
+ if(!ClearIsOnline(Connection->WorldID, &NumAffectedCharacters)){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+
+ TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK);
+ WriteBuffer.Write16((uint16)NumAffectedCharacters);
+ SendResponse(Connection, &WriteBuffer);
}
void ProcessCreatePlayerlistQuery(TConnection *Connection, TReadBuffer *Buffer){
- SendQueryStatusFailed(Connection);
+ if(Connection->ApplicationType != APPLICATION_TYPE_GAME){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+
+ // TODO(fusion): Have some TransactionGuard type that will either commit or
+ // rollback a transaction when we leave its scope, based on whether some
+ // `Commit` function was called.
+
+ if(!DeleteOnlineCharacters(Connection->WorldID)){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+
+ bool NewRecord = false;
+ int NumCharacters = Buffer->Read16();
+ if(NumCharacters != 0xFFFF && NumCharacters > 0){
+ TOnlineCharacter *Characters = (TOnlineCharacter*)alloca(NumCharacters * sizeof(TOnlineCharacter));
+ for(int i = 0; i < NumCharacters; i += 1){
+ Buffer->ReadString(Characters[i].Name, sizeof(Characters[i].Name));
+ Characters[i].Level = Buffer->Read16();
+ Buffer->ReadString(Characters[i].Profession, sizeof(Characters[i].Profession));
+ }
+
+ if(!InsertOnlineCharacters(Connection->WorldID, NumCharacters, Characters)){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+
+ if(!CheckOnlineRecord(Connection->WorldID, NumCharacters, &NewRecord)){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+ }
+
+ TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK);
+ WriteBuffer.WriteFlag(NewRecord);
+ SendResponse(Connection, &WriteBuffer);
}
void ProcessLogKilledCreaturesQuery(TConnection *Connection, TReadBuffer *Buffer){
@@ -622,7 +720,29 @@ void ProcessLogKilledCreaturesQuery(TConnection *Connection, TReadBuffer *Buffer
}
void ProcessLoadPlayersQuery(TConnection *Connection, TReadBuffer *Buffer){
- SendQueryStatusFailed(Connection);
+ if(Connection->ApplicationType != APPLICATION_TYPE_GAME){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+
+ // IMPORTANT(fusion): The server expect 10K entries at most. It is probably
+ // some shared hard coded constant.
+ int NumEntries;
+ TCharacterIndexEntry Entries[10000];
+ int MinimumCharacterID = (int)Buffer->Read32();
+ if(!LoadCharacterIndex(Connection->WorldID,
+ MinimumCharacterID, NARRAY(Entries), &NumEntries, Entries)){
+ SendQueryStatusFailed(Connection);
+ return;
+ }
+
+ TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK);
+ WriteBuffer.Write32((uint32)NumEntries);
+ for(int i = 0; i < NumEntries; i += 1){
+ WriteBuffer.WriteString(Entries[i].Name);
+ WriteBuffer.Write32((uint32)Entries[i].CharacterID);
+ }
+ SendResponse(Connection, &WriteBuffer);
}
void ProcessExcludeFromAuctionsQuery(TConnection *Connection, TReadBuffer *Buffer){
@@ -640,8 +760,7 @@ void ProcessLoadWorldConfigQuery(TConnection *Connection, TReadBuffer *Buffer){
}
TWorldConfig WorldConfig = {};
- const char *WorldName = Connection->ApplicationData;
- if(!LoadWorldConfig(WorldName, &WorldConfig)){
+ if(!LoadWorldConfig(Connection->WorldID, &WorldConfig)){
SendQueryStatusFailed(Connection);
return;
}
diff --git a/src/database.cc b/src/database.cc
index d9c2815..429f357 100644
--- a/src/database.cc
+++ b/src/database.cc
@@ -100,18 +100,46 @@ void ExitStatementCache(void){
// Queries
//==============================================================================
+bool LoadWorldID(const char *WorldName, int *WorldID){
+ ASSERT(WorldName && WorldID);
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "SELECT WorldID FROM Worlds WHERE Name = ?1");
+ if(Stmt == NULL){
+ LOG_ERR("Failed to prepare query");
+ return false;
+ }
+
+ if(sqlite3_bind_text(Stmt, 1, WorldName, -1, NULL) != SQLITE_OK){
+ LOG_ERR("Failed to bind WorldName: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
-bool LoadHouseOwners(const char *WorldName, DynamicArray<THouseOwner> *HouseOwners){
- ASSERT(WorldName && HouseOwners);
+ if(sqlite3_step(Stmt) != SQLITE_ROW){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ *WorldID = sqlite3_column_int(Stmt, 0);
+ return true;
+}
+
+bool LoadHouseOwners(int WorldID, DynamicArray<THouseOwner> *HouseOwners){
+ ASSERT(HouseOwners);
sqlite3_stmt *Stmt = PrepareQuery(
- "SELECT HouseID, OwnerID, Characters.Name, PaidUntil"
- " FROM HouseOwners"
- " LEFT JOIN Characters ON Characters.CharacterID = OwnerID");
+ "SELECT O.HouseID, O.OwnerID, C.Name, O.PaidUntil"
+ " FROM HouseOwners AS O"
+ " LEFT JOIN Characters AS C ON C.CharacterID = O.OwnerID"
+ " WHERE O.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){
THouseOwner Owner = {};
Owner.HouseID = sqlite3_column_int(Stmt, 0);
@@ -130,19 +158,237 @@ bool LoadHouseOwners(const char *WorldName, DynamicArray<THouseOwner> *HouseOwne
return true;
}
-bool LoadWorldConfig(const char *WorldName, TWorldConfig *WorldConfig){
- ASSERT(WorldName && WorldConfig);
+bool DeleteHouses(int WorldID){
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "DELETE FROM Houses 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;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return true;
+}
+
+bool InsertHouses(int WorldID, int NumHouses, THouse *Houses){
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "INSERT INTO Houses (WorldID, HouseID, Name, Rent, Description,"
+ " Size, PositionX, PositionY, PositionZ, Town, GuildHouse)"
+ " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)");
+ 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;
+ }
+
+ for(int i = 0; i < NumHouses; i += 1){
+ if(sqlite3_bind_int(Stmt, 2, Houses[i].HouseID) != SQLITE_OK
+ || sqlite3_bind_text(Stmt, 3, Houses[i].Name, -1, NULL) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 4, Houses[i].Rent) != SQLITE_OK
+ || sqlite3_bind_text(Stmt, 5, Houses[i].Description, -1, NULL) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 6, Houses[i].Size) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 7, Houses[i].PositionX) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 8, Houses[i].PositionY) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 9, Houses[i].PositionZ) != SQLITE_OK
+ || sqlite3_bind_text(Stmt, 10, Houses[i].Town, -1, NULL) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 11, Houses[i].GuildHouse ? 1: 0) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters for house %d: %s",
+ Houses[i].HouseID, sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to insert house %d: %s",
+ Houses[i].HouseID, sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ sqlite3_reset(Stmt);
+ }
+
+ return true;
+}
+
+bool ClearIsOnline(int WorldID, int *NumAffectedCharacters){
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "UPDATE Characters SET IsOnline = 0"
+ " WHERE WorldID = ?1 AND IsOnline != 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;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ *NumAffectedCharacters = sqlite3_changes(g_Database);
+ return true;
+}
+
+bool DeleteOnlineCharacters(int WorldID){
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "DELETE FROM OnlineCharacters 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;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ return true;
+}
+
+bool InsertOnlineCharacters(int WorldID, int NumCharacters, TOnlineCharacter *Characters){
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "INSERT INTO OnlineCharacters (WorldID, Name, Level, Profession)"
+ " 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){
+ LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ for(int i = 0; i < NumCharacters; i += 1){
+ if(sqlite3_bind_text(Stmt, 2, Characters[i].Name, -1, NULL) != SQLITE_OK
+ || sqlite3_bind_int(Stmt, 3, Characters[i].Level) != SQLITE_OK
+ || sqlite3_bind_text(Stmt, 4, Characters[i].Profession, -1, NULL) != SQLITE_OK){
+ LOG_ERR("Failed to bind parameters for character \"%s\": %s",
+ Characters[i].Name, sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ if(sqlite3_step(Stmt) != SQLITE_DONE){
+ LOG_ERR("Failed to insert character \"%s\": %s",
+ Characters[i].Name, sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ sqlite3_reset(Stmt);
+ }
+
+ return true;
+}
+
+bool CheckOnlineRecord(int WorldID, int NumCharacters, bool *NewRecord){
+ ASSERT(NewRecord);
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "UPDATE Worlds SET OnlineRecord = ?2"
+ " WHERE WorldID = ?1 AND OnlineRecord < ?2");
+ 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;
+ }
+
+ if(sqlite3_bind_int(Stmt, 2, NumCharacters) != SQLITE_OK){
+ LOG_ERR("Failed to bind NumCharacters: %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;
+ }
+
+ *NewRecord = sqlite3_changes(g_Database) > 0;
+ return true;
+}
+
+bool LoadCharacterIndex(int WorldID, int MinimumCharacterID,
+ int MaxEntries, int *NumEntries, TCharacterIndexEntry *Entries){
+ ASSERT(MaxEntries > 0 && NumEntries && Entries);
+ sqlite3_stmt *Stmt = PrepareQuery(
+ "SELECT CharacterID, Name FROM Characters"
+ " WHERE WorldID = ?1 AND CharacterID >= ?2"
+ " ORDER BY CharacterID ASC LIMIT ?3");
+ 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;
+ }
+
+ 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));
+ return false;
+ }
+
+ // NOTE(fusion): We shouldn't get more than `MaxEntries` rows but it's
+ // always better to be safe.
+ int EntryIndex = 0;
+ while(sqlite3_step(Stmt) == SQLITE_ROW && EntryIndex < MaxEntries){
+ Entries[EntryIndex].CharacterID = sqlite3_column_int(Stmt, 0);
+ StringCopy(Entries[EntryIndex].Name,
+ sizeof(Entries[EntryIndex].Name),
+ (const char *)sqlite3_column_text(Stmt, 1));
+ }
+
+ if(sqlite3_errcode(g_Database) != SQLITE_DONE){
+ LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database));
+ return false;
+ }
+
+ *NumEntries = EntryIndex;
+ return true;
+}
+
+bool LoadWorldConfig(int WorldID, TWorldConfig *WorldConfig){
+ ASSERT(WorldConfig);
sqlite3_stmt *Stmt = PrepareQuery(
"SELECT Type, RebootTime, Address, Port, MaxPlayers,"
" PremiumPlayerBuffer, MaxNewbies, PremiumNewbieBuffer"
- " FROM Worlds WHERE Name = ?1");
+ " FROM Worlds WHERE WorldID = ?1");
if(Stmt == NULL){
LOG_ERR("Failed to prepare query");
return false;
}
- if(sqlite3_bind_text(Stmt, 1, WorldName, -1, NULL) != SQLITE_OK){
- LOG_ERR("Failed to bind WorldName: %s", sqlite3_errmsg(g_Database));
+ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){
+ LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database));
return false;
}
diff --git a/src/querymanager.hh b/src/querymanager.hh
index b1b197d..2eab216 100644
--- a/src/querymanager.hh
+++ b/src/querymanager.hh
@@ -168,6 +168,10 @@ inline void BufferWrite32BE(uint8 *Buffer, uint32 Value){
}
struct TReadBuffer{
+ uint8 *Buffer;
+ int Size;
+ int Position;
+
TReadBuffer(uint8 *Buffer, int Size)
: Buffer(Buffer), Size(Size), Position(0) {}
@@ -179,6 +183,10 @@ struct TReadBuffer{
return this->Position > this->Size;
}
+ bool ReadFlag(void){
+ return this->Read8() != 0x00;
+ }
+
uint8 Read8(void){
uint8 Result = 0;
if(this->CanRead(1)){
@@ -241,15 +249,13 @@ struct TReadBuffer{
this->Position += Length;
}
+};
- // DATA
- // =================
+struct TWriteBuffer{
uint8 *Buffer;
int Size;
int Position;
-};
-struct TWriteBuffer{
TWriteBuffer(uint8 *Buffer, int Size)
: Buffer(Buffer), Size(Size), Position(0) {}
@@ -261,6 +267,10 @@ struct TWriteBuffer{
return this->Position > this->Size;
}
+ void WriteFlag(bool Value){
+ this->Write8(Value ? 0x01 : 0x00);
+ }
+
void Write8(uint8 Value){
if(this->CanWrite(1)){
BufferWrite8(this->Buffer + this->Position, Value);
@@ -334,12 +344,6 @@ struct TWriteBuffer{
this->Position += 4;
}
}
-
- // DATA
- // =================
- uint8 *Buffer;
- int Size;
- int Position;
};
// Dynamic Array
@@ -565,7 +569,7 @@ struct TConnection{
uint8 *Buffer;
bool Authorized;
int ApplicationType;
- char ApplicationData[30];
+ int WorldID;
char RemoteAddress[30];
};
@@ -649,6 +653,30 @@ struct THouseOwner{
int PaidUntil;
};
+struct THouse{
+ int HouseID;
+ char Name[50];
+ int Rent;
+ char Description[500];
+ int Size;
+ int PositionX;
+ int PositionY;
+ int PositionZ;
+ char Town[30];
+ bool GuildHouse;
+};
+
+struct TOnlineCharacter{
+ char Name[30];
+ int Level;
+ char Profession[30];
+};
+
+struct TCharacterIndexEntry{
+ char Name[30];
+ int CharacterID;
+};
+
struct TWorldConfig{
int Type;
int RebootTime;
@@ -660,8 +688,18 @@ struct TWorldConfig{
int PremiumNewbieBuffer;
};
-bool LoadHouseOwners(const char *WorldName, DynamicArray<THouseOwner> *HouseOwners);
-bool LoadWorldConfig(const char *WorldName, TWorldConfig *WorldConfig);
+bool LoadWorldID(const char *WorldName, int *WorldID);
+//bool DecrementIsOnline(int WorldID, int CharacterID);
+bool LoadHouseOwners(int WorldID, DynamicArray<THouseOwner> *HouseOwners);
+bool DeleteHouses(int WorldID);
+bool InsertHouses(int WorldID, int NumHouses, THouse *Houses);
+bool ClearIsOnline(int WorldID, int *NumAffectedCharacters);
+bool DeleteOnlineCharacters(int WorldID);
+bool InsertOnlineCharacters(int WorldID, int NumCharacters, TOnlineCharacter *Characters);
+bool CheckOnlineRecord(int WorldID, int NumCharacters, bool *NewRecord);
+bool LoadCharacterIndex(int WorldID, int MinimumCharacterID,
+ int MaxEntries, int *NumEntries, TCharacterIndexEntry *Entries);
+bool LoadWorldConfig(int WorldID, TWorldConfig *WorldConfig);
bool InitDatabase(void);
void ExitDatabase(void);