From d85fa6c12cd135cca786847f204a66aa82f6a360 Mon Sep 17 00:00:00 2001 From: server Date: Tue, 14 Apr 2026 12:42:05 +0200 Subject: [PATCH] db: prepare player create flow --- src/db/ClientManagerPlayer.cpp | 302 ++++++++++++++++++++++----------- src/libsql/Statement.cpp | 16 ++ src/libsql/Statement.h | 2 + 3 files changed, 223 insertions(+), 97 deletions(-) diff --git a/src/db/ClientManagerPlayer.cpp b/src/db/ClientManagerPlayer.cpp index 0b59fa1..36c21dc 100644 --- a/src/db/ClientManagerPlayer.cpp +++ b/src/db/ClientManagerPlayer.cpp @@ -8,6 +8,7 @@ #include "ItemAwardManager.h" #include "HB.h" #include "Cache.h" +#include "libsql/Statement.h" extern bool g_bHotBackup; @@ -15,6 +16,181 @@ extern std::string g_stLocale; extern int g_test_server; extern int g_log; +namespace +{ +bool PreparePlayerStmt(CStmt& stmt, const char* query) +{ + CAsyncSQL* sql = CDBManager::instance().GetDirectSQL(SQL_PLAYER); + + if (!sql) + { + sys_err("player SQL handle is not initialized"); + return false; + } + + return stmt.Prepare(sql, query); +} + +bool LoadPlayerIndexSlot(DWORD accountId, BYTE accountIndex, DWORD* playerId, bool* foundRow) +{ + char query[QUERY_MAX_LEN]; + snprintf(query, sizeof(query), "SELECT pid%u FROM player_index%s WHERE id = ?", accountIndex + 1, GetTablePostfix()); + + *playerId = 0; + *foundRow = false; + + CStmt stmt; + if (!PreparePlayerStmt(stmt, query)) + return false; + + if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId)) + return false; + + if (!stmt.BindResult(MYSQL_TYPE_LONG, playerId)) + return false; + + if (!stmt.Execute()) + return false; + + if (stmt.iRows == 0) + return true; + + if (!stmt.Fetch()) + return false; + + *foundRow = true; + return true; +} + +bool CountPlayersByName(const char* playerName, unsigned long long* count) +{ + char query[QUERY_MAX_LEN]; + + if (g_stLocale == "sjis") + snprintf(query, sizeof(query), + "SELECT COUNT(*) FROM player%s WHERE name = ? COLLATE sjis_japanese_ci", + GetTablePostfix()); + else + snprintf(query, sizeof(query), + "SELECT COUNT(*) FROM player%s WHERE name = ?", + GetTablePostfix()); + + *count = 0; + + CStmt stmt; + if (!PreparePlayerStmt(stmt, query)) + return false; + + if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast(playerName), CHARACTER_NAME_MAX_LEN + 1)) + return false; + + if (!stmt.BindResult(MYSQL_TYPE_LONGLONG, count)) + return false; + + if (!stmt.Execute()) + return false; + + if (stmt.iRows == 0) + return false; + + return stmt.Fetch(); +} + +bool InsertPlayerRecord(const TPlayerCreatePacket* packet, DWORD* playerId) +{ + char query[QUERY_MAX_LEN]; + snprintf(query, sizeof(query), + "INSERT INTO player%s " + "(id, account_id, name, level, st, ht, dx, iq, " + "job, voice, dir, x, y, z, " + "hp, mp, random_hp, random_sp, stat_point, stamina, part_base, part_main, part_hair, gold, playtime, " + "skill_level, quickslot) " + "VALUES(0, %u, ?, %d, %d, %d, %d, %d, " + "%d, %d, %d, %d, %d, %d, %d, " + "%d, %d, %d, %d, %d, %d, %d, 0, %d, 0, ?, ?)", + GetTablePostfix(), + packet->account_id, + packet->player_table.level, + packet->player_table.st, + packet->player_table.ht, + packet->player_table.dx, + packet->player_table.iq, + packet->player_table.job, + packet->player_table.voice, + packet->player_table.dir, + packet->player_table.x, + packet->player_table.y, + packet->player_table.z, + packet->player_table.hp, + packet->player_table.sp, + packet->player_table.sRandomHP, + packet->player_table.sRandomSP, + packet->player_table.stat_point, + packet->player_table.stamina, + packet->player_table.part_base, + packet->player_table.part_base, + packet->player_table.gold); + + CStmt stmt; + if (!PreparePlayerStmt(stmt, query)) + return false; + + if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast(packet->player_table.name), CHARACTER_NAME_MAX_LEN + 1)) + return false; + + if (!stmt.BindParam(MYSQL_TYPE_BLOB, const_cast(packet->player_table.skills), sizeof(packet->player_table.skills))) + return false; + + if (!stmt.BindParam(MYSQL_TYPE_BLOB, const_cast(packet->player_table.quickslot), sizeof(packet->player_table.quickslot))) + return false; + + if (!stmt.Execute()) + return false; + + if (stmt.GetAffectedRows() == 0) + return false; + + *playerId = static_cast(stmt.GetInsertId()); + return *playerId != 0; +} + +bool UpdatePlayerIndexSlot(DWORD accountId, BYTE accountIndex, DWORD playerId) +{ + char query[QUERY_MAX_LEN]; + snprintf(query, sizeof(query), "UPDATE player_index%s SET pid%u = ? WHERE id = ?", GetTablePostfix(), accountIndex + 1); + + CStmt stmt; + if (!PreparePlayerStmt(stmt, query)) + return false; + + if (!stmt.BindParam(MYSQL_TYPE_LONG, &playerId)) + return false; + + if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId)) + return false; + + if (!stmt.Execute()) + return false; + + return stmt.GetAffectedRows() != 0; +} + +void DeletePlayerById(DWORD playerId) +{ + char query[QUERY_MAX_LEN]; + snprintf(query, sizeof(query), "DELETE FROM player%s WHERE id = ?", GetTablePostfix()); + + CStmt stmt; + if (!PreparePlayerStmt(stmt, query)) + return; + + if (!stmt.BindParam(MYSQL_TYPE_LONG, &playerId)) + return; + + stmt.Execute(); +} +} + // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // !!!!!!!!!!! IMPORTANT !!!!!!!!!!!! @@ -810,9 +986,7 @@ static time_by_id_map_t s_createTimeByAccountID; */ void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerCreatePacket* packet) { - char queryStr[QUERY_MAX_LEN]; - int queryLen; - int player_id; + DWORD player_id = 0; // 한 계정에 X초 내로 캐릭터 생성을 할 수 없다. time_by_id_map_t::iterator it = s_createTimeByAccountID.find(packet->account_id); @@ -828,81 +1002,40 @@ void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerC } } - queryLen = snprintf(queryStr, sizeof(queryStr), - "SELECT pid%u FROM player_index%s WHERE id=%d", packet->account_index + 1, GetTablePostfix(), packet->account_id); - - auto pMsg0 = CDBManager::instance().DirectQuery(queryStr); - - if (pMsg0->Get()->uiNumRows != 0) - { - if (!pMsg0->Get()->pSQLResult) - { - peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0); - return; - } - - MYSQL_ROW row = mysql_fetch_row(pMsg0->Get()->pSQLResult); - - DWORD dwPID = 0; str_to_number(dwPID, row[0]); - if (row[0] && dwPID > 0) - { - peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0); - sys_log(0, "ALREADY EXIST AccountChrIdx %d ID %d", packet->account_index, dwPID); - return; - } - } - else + DWORD existingPlayerId = 0; + bool foundPlayerIndexRow = false; + if (!LoadPlayerIndexSlot(packet->account_id, packet->account_index, &existingPlayerId, &foundPlayerIndexRow)) { peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0); return; } - if (g_stLocale == "sjis") - snprintf(queryStr, sizeof(queryStr), - "SELECT COUNT(*) as count FROM player%s WHERE name='%s' collate sjis_japanese_ci", - GetTablePostfix(), packet->player_table.name); - else - snprintf(queryStr, sizeof(queryStr), - "SELECT COUNT(*) as count FROM player%s WHERE name='%s'", GetTablePostfix(), packet->player_table.name); - - auto pMsg1 = CDBManager::instance().DirectQuery(queryStr); - - if (pMsg1->Get()->uiNumRows) - { - if (!pMsg1->Get()->pSQLResult) - { - peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0); - return; - } - - MYSQL_ROW row = mysql_fetch_row(pMsg1->Get()->pSQLResult); - - if (*row[0] != '0') - { - sys_log(0, "ALREADY EXIST name %s, row[0] %s query %s", packet->player_table.name, row[0], queryStr); - peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0); - return; - } - } - else + if (!foundPlayerIndexRow) { peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0); return; } - queryLen = snprintf(queryStr, sizeof(queryStr), - "INSERT INTO player%s " - "(id, account_id, name, level, st, ht, dx, iq, " - "job, voice, dir, x, y, z, " - "hp, mp, random_hp, random_sp, stat_point, stamina, part_base, part_main, part_hair, gold, playtime, " - "skill_level, quickslot) " - "VALUES(0, %u, '%s', %d, %d, %d, %d, %d, " - "%d, %d, %d, %d, %d, %d, %d, " - "%d, %d, %d, %d, %d, %d, %d, 0, %d, 0, ", - GetTablePostfix(), - packet->account_id, packet->player_table.name, packet->player_table.level, packet->player_table.st, packet->player_table.ht, packet->player_table.dx, packet->player_table.iq, - packet->player_table.job, packet->player_table.voice, packet->player_table.dir, packet->player_table.x, packet->player_table.y, packet->player_table.z, - packet->player_table.hp, packet->player_table.sp, packet->player_table.sRandomHP, packet->player_table.sRandomSP, packet->player_table.stat_point, packet->player_table.stamina, packet->player_table.part_base, packet->player_table.part_base, packet->player_table.gold); + if (existingPlayerId > 0) + { + peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0); + sys_log(0, "ALREADY EXIST AccountChrIdx %d ID %d", packet->account_index, existingPlayerId); + return; + } + + unsigned long long playerCount = 0; + if (!CountPlayersByName(packet->player_table.name, &playerCount)) + { + peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0); + return; + } + + if (playerCount != 0) + { + sys_log(0, "ALREADY EXIST name %s", packet->player_table.name); + peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0); + return; + } sys_log(0, "PlayerCreate accountid %d name %s level %d gold %d, st %d ht %d job %d", packet->account_id, @@ -913,40 +1046,16 @@ void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerC packet->player_table.ht, packet->player_table.job); - static char text[8192 + 1]; - - CDBManager::instance().EscapeString(text, packet->player_table.skills, sizeof(packet->player_table.skills)); - queryLen += snprintf(queryStr + queryLen, sizeof(queryStr) - queryLen, "'%s', ", text); - if (g_test_server) - sys_log(0, "Create_Player queryLen[%d] TEXT[%s]", queryLen, text); - - CDBManager::instance().EscapeString(text, packet->player_table.quickslot, sizeof(packet->player_table.quickslot)); - queryLen += snprintf(queryStr + queryLen, sizeof(queryStr) - queryLen, "'%s')", text); - - auto pMsg2 = CDBManager::instance().DirectQuery(queryStr); - if (g_test_server) - sys_log(0, "Create_Player queryLen[%d] TEXT[%s]", queryLen, text); - - if (pMsg2->Get()->uiAffectedRows <= 0) + if (!InsertPlayerRecord(packet, &player_id)) { peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0); - sys_log(0, "ALREADY EXIST3 query: %s AffectedRows %lu", queryStr, pMsg2->Get()->uiAffectedRows); + sys_log(0, "ALREADY EXIST3 name %s", packet->player_table.name); return; } - player_id = pMsg2->Get()->uiInsertID; - - snprintf(queryStr, sizeof(queryStr), "UPDATE player_index%s SET pid%d=%d WHERE id=%d", - GetTablePostfix(), packet->account_index + 1, player_id, packet->account_id); - auto pMsg3 = CDBManager::instance().DirectQuery(queryStr); - - if (pMsg3->Get()->uiAffectedRows <= 0) + if (!UpdatePlayerIndexSlot(packet->account_id, packet->account_index, player_id)) { - sys_err("QUERY_ERROR: %s", queryStr); - - snprintf(queryStr, sizeof(queryStr), "DELETE FROM player%s WHERE id=%d", GetTablePostfix(), player_id); - CDBManager::instance().DirectQuery(queryStr); - + DeletePlayerById(player_id); peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0); return; } @@ -1372,4 +1481,3 @@ void CClientManager::FlushPlayerCacheSet(DWORD pid) delete c; } } - diff --git a/src/libsql/Statement.cpp b/src/libsql/Statement.cpp index 7a3e605..0a219a5 100644 --- a/src/libsql/Statement.cpp +++ b/src/libsql/Statement.cpp @@ -171,3 +171,19 @@ bool CStmt::Fetch() { return !mysql_stmt_fetch(m_pkStmt); } + +unsigned long long CStmt::GetInsertId() const +{ + if (!m_pkStmt) + return 0; + + return mysql_stmt_insert_id(m_pkStmt); +} + +unsigned long long CStmt::GetAffectedRows() const +{ + if (!m_pkStmt) + return 0; + + return mysql_stmt_affected_rows(m_pkStmt); +} diff --git a/src/libsql/Statement.h b/src/libsql/Statement.h index b869af1..c0d589b 100644 --- a/src/libsql/Statement.h +++ b/src/libsql/Statement.h @@ -17,6 +17,8 @@ class CStmt bool BindResult(enum_field_types type, void * p, int iMaxLen=0); int Execute(); bool Fetch(); + unsigned long long GetInsertId() const; + unsigned long long GetAffectedRows() const; void Error(const char * c_pszMsg);