From 3ab6d1d312e2b1ecbd858be56bd7a5d6bc451a85 Mon Sep 17 00:00:00 2001 From: fusion32 Date: Fri, 11 Jul 2025 11:55:03 -0300 Subject: implement more queries + overall improvements --- Makefile | 2 - sql/schema.sql | 42 ++++++--- src/connections.cc | 147 ++++++++++++++++++++++++++--- src/database.cc | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++-- src/querymanager.hh | 64 ++++++++++--- 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 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 *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 *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 *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 *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 *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); -- cgit v1.2.3