feat(net): implement character creation (request and response)

This commit is contained in:
VDm 2025-06-22 15:45:12 +04:00
parent 3fb86ab12e
commit f67c8cfc04
13 changed files with 167 additions and 16 deletions

View File

@ -248,6 +248,11 @@ void ClientServices::CharacterDelete(uint64_t guid) {
ClientServices::s_currentConnection->DeleteCharacter(guid); ClientServices::s_currentConnection->DeleteCharacter(guid);
} }
void ClientServices::RequestCharacterCreate(const CHARACTER_CREATE_INFO* info) {
STORM_ASSERT(ClientServices::s_currentConnection);
ClientServices::s_currentConnection->CharacterCreate(info);
}
void ClientServices::Initialize() { void ClientServices::Initialize() {
if (!g_clientConnection) { if (!g_clientConnection) {
ClientServices::s_clientRealmResponse = NEW(ClientRealmResponseAdapter); ClientServices::s_clientRealmResponse = NEW(ClientRealmResponseAdapter);
@ -427,6 +432,16 @@ int32_t ClientServices::GetExpansionLevel() {
return ClientServices::Connection()->GetExpansionLevel(); return ClientServices::Connection()->GetExpansionLevel();
} }
uint32_t ClientServices::CharacterValidateName(const char* name) {
// WORKAROUND:
if (!name || *name == '\0') {
return CHAR_NAME_RESULT_START + NAME_NO_NAME;
}
// TODO
return CHAR_NAME_SUCCESS;
}
void ClientServices::InitLoginServerCVars(int32_t overwrite, const char* locale) { void ClientServices::InitLoginServerCVars(int32_t overwrite, const char* locale) {
if ((ClientServices::s_realmListBNVar == nullptr || ClientServices::s_realmListVar == nullptr) || overwrite != 0 ) { if ((ClientServices::s_realmListBNVar == nullptr || ClientServices::s_realmListVar == nullptr) || overwrite != 0 ) {
ClientServices::s_decorateAccountName = CVar::Register( ClientServices::s_decorateAccountName = CVar::Register(

View File

@ -46,6 +46,7 @@ class ClientServices : public LoginResponse {
static const char* GetSelectedRealmName(); static const char* GetSelectedRealmName();
static const REALM_INFO* GetSelectedRealm(); static const REALM_INFO* GetSelectedRealm();
static void CharacterDelete(uint64_t guid); static void CharacterDelete(uint64_t guid);
static void RequestCharacterCreate(const CHARACTER_CREATE_INFO* info);
static void Initialize(); static void Initialize();
static Login* LoginConnection(); static Login* LoginConnection();
static void Logon(const char* accountName, const char* password); static void Logon(const char* accountName, const char* password);
@ -57,6 +58,7 @@ class ClientServices : public LoginResponse {
static const char* GetDefaultPatchListString(); static const char* GetDefaultPatchListString();
static bool LoadCDKey(); static bool LoadCDKey();
static int32_t GetExpansionLevel(); static int32_t GetExpansionLevel();
static uint32_t CharacterValidateName(const char* name);
// Virtual member functions // Virtual member functions
virtual int32_t GetLoginServerType(); virtual int32_t GetLoginServerType();

View File

@ -40,7 +40,7 @@ ComponentData::ComponentData(const CHARACTER_INFO& info) {
this->m_info.faceID = info.faceID; this->m_info.faceID = info.faceID;
this->m_info.hairStyleID = info.hairStyleID; this->m_info.hairStyleID = info.hairStyleID;
this->m_info.hairColorID = info.hairColorID; this->m_info.hairColorID = info.hairColorID;
this->m_info.facialFeatureID = info.facialHairStyleID; this->m_info.facialHairStyleID = info.facialHairStyleID;
DefaultGeosets(); DefaultGeosets();
} }
@ -210,7 +210,7 @@ void CCharacterComponent::GeosRenderPrep(int32_t a2) {
} }
} }
void CCharacterComponent::GetInfo(CHARACTER_CREATE_INFO* info) { void CCharacterComponent::GetInfo(CHARACTER_PREFERENCES* info) {
if (info) { if (info) {
*info = this->m_data.m_info; *info = this->m_data.m_info;
} }

View File

@ -17,7 +17,7 @@ class ComponentData {
void DefaultGeosets(); void DefaultGeosets();
public: public:
CHARACTER_CREATE_INFO m_info; CHARACTER_PREFERENCES m_info;
CM2Model* m_model; CM2Model* m_model;
uint32_t m_unkFlag; uint32_t m_unkFlag;
uint32_t m_geosets[19]; uint32_t m_geosets[19];
@ -60,7 +60,7 @@ class CCharacterComponent {
bool Init(ComponentData* data, const char* a3); bool Init(ComponentData* data, const char* a3);
bool RenderPrep(int32_t a2); bool RenderPrep(int32_t a2);
void GeosRenderPrep(int32_t a2); void GeosRenderPrep(int32_t a2);
void GetInfo(CHARACTER_CREATE_INFO* info); void GetInfo(CHARACTER_PREFERENCES* info);
public: public:
uint32_t m_handle; uint32_t m_handle;

View File

@ -1,15 +1,17 @@
#include "glue/CCharacterCreation.hpp" #include "glue/CCharacterCreation.hpp"
#include "glue/CCharacterComponent.hpp" #include "glue/CCharacterComponent.hpp"
#include "glue/CCharacterSelection.hpp" #include "glue/CCharacterSelection.hpp"
#include "glue/CGlueMgr.hpp"
#include "ui/CSimpleModelFFX.hpp" #include "ui/CSimpleModelFFX.hpp"
#include "model/CM2Model.hpp" #include "model/CM2Model.hpp"
#include "model/CM2Shared.hpp" #include "model/CM2Shared.hpp"
#include "clientobject/Player_C.hpp" #include "clientobject/Player_C.hpp"
#include "client/ClientServices.hpp"
#include "db/Db.hpp" #include "db/Db.hpp"
int32_t CCharacterCreation::m_selectedClassID; int32_t CCharacterCreation::m_selectedClassID;
int32_t CCharacterCreation::m_existingCharacterIndex; int32_t CCharacterCreation::m_existingCharacterIndex;
CHARACTER_CREATE_INFO* CCharacterCreation::m_charPreferences[44]; CHARACTER_PREFERENCES* CCharacterCreation::m_charPreferences[44];
int32_t CCharacterCreation::m_raceIndex; int32_t CCharacterCreation::m_raceIndex;
CSimpleModelFFX* CCharacterCreation::m_charCustomizeFrame; CSimpleModelFFX* CCharacterCreation::m_charCustomizeFrame;
float CCharacterCreation::m_charFacing; float CCharacterCreation::m_charFacing;
@ -225,7 +227,7 @@ void CCharacterCreation::InitCharacterComponent(ComponentData* data, int32_t ran
CCharacterCreation::m_prevFaceIndex = info.faceID; CCharacterCreation::m_prevFaceIndex = info.faceID;
CCharacterCreation::m_prevHairColorIndex = info.hairColorID; CCharacterCreation::m_prevHairColorIndex = info.hairColorID;
CCharacterCreation::m_prevHairStyleIndex = info.hairStyleID; CCharacterCreation::m_prevHairStyleIndex = info.hairStyleID;
CCharacterCreation::m_prevFacialFeatureIndex = info.facialFeatureID; CCharacterCreation::m_prevFacialFeatureIndex = info.facialHairStyleID;
CCharacterCreation::SetCharFacing(CCharacterCreation::m_charFacing); CCharacterCreation::SetCharFacing(CCharacterCreation::m_charFacing);
CCharacterCreation::Dress(); CCharacterCreation::Dress();
@ -251,7 +253,7 @@ void CCharacterCreation::RandomizeCharFeatures() {
CCharacterCreation::m_prevFaceIndex = info.faceID; CCharacterCreation::m_prevFaceIndex = info.faceID;
CCharacterCreation::m_prevHairColorIndex = info.hairColorID; CCharacterCreation::m_prevHairColorIndex = info.hairColorID;
CCharacterCreation::m_prevHairStyleIndex = info.hairStyleID; CCharacterCreation::m_prevHairStyleIndex = info.hairStyleID;
CCharacterCreation::m_prevFacialFeatureIndex = info.facialFeatureID; CCharacterCreation::m_prevFacialFeatureIndex = info.facialHairStyleID;
} }
void CCharacterCreation::SetSelectedRace(int32_t raceIndex) { void CCharacterCreation::SetSelectedRace(int32_t raceIndex) {
@ -266,7 +268,7 @@ void CCharacterCreation::SetSelectedRace(int32_t raceIndex) {
auto preferences = CCharacterCreation::m_charPreferences[2 * previousRace + sexID]; auto preferences = CCharacterCreation::m_charPreferences[2 * previousRace + sexID];
if (!preferences) { if (!preferences) {
preferences = NEW(CHARACTER_CREATE_INFO); preferences = NEW(CHARACTER_PREFERENCES);
CCharacterCreation::m_charPreferences[2 * previousRace + sexID] = preferences; CCharacterCreation::m_charPreferences[2 * previousRace + sexID] = preferences;
} }
@ -285,7 +287,7 @@ void CCharacterCreation::SetSelectedRace(int32_t raceIndex) {
data.m_info.skinID = display->m_characterInfo.skinID; data.m_info.skinID = display->m_characterInfo.skinID;
data.m_info.hairStyleID = display->m_characterInfo.hairStyleID; data.m_info.hairStyleID = display->m_characterInfo.hairStyleID;
data.m_info.hairColorID = display->m_characterInfo.hairColorID; data.m_info.hairColorID = display->m_characterInfo.hairColorID;
data.m_info.facialFeatureID = display->m_characterInfo.facialHairStyleID; data.m_info.facialHairStyleID = display->m_characterInfo.facialHairStyleID;
data.m_info.faceID = display->m_characterInfo.faceID; data.m_info.faceID = display->m_characterInfo.faceID;
CCharacterCreation::InitCharacterComponent(&data, 0); CCharacterCreation::InitCharacterComponent(&data, 0);
@ -340,7 +342,7 @@ void CCharacterCreation::SetSelectedSex(int32_t sexID) {
auto preferences = CCharacterCreation::m_charPreferences[2 * raceID + previousSex]; auto preferences = CCharacterCreation::m_charPreferences[2 * raceID + previousSex];
if (!preferences) { if (!preferences) {
preferences = NEW(CHARACTER_CREATE_INFO); preferences = NEW(CHARACTER_PREFERENCES);
CCharacterCreation::m_charPreferences[2 * raceID + previousSex] = preferences; CCharacterCreation::m_charPreferences[2 * raceID + previousSex] = preferences;
} }
@ -361,7 +363,7 @@ void CCharacterCreation::SetSelectedSex(int32_t sexID) {
data.m_info.skinID = display->m_characterInfo.skinID; data.m_info.skinID = display->m_characterInfo.skinID;
data.m_info.hairStyleID = display->m_characterInfo.hairStyleID; data.m_info.hairStyleID = display->m_characterInfo.hairStyleID;
data.m_info.hairColorID = display->m_characterInfo.hairColorID; data.m_info.hairColorID = display->m_characterInfo.hairColorID;
data.m_info.facialFeatureID = display->m_characterInfo.facialHairStyleID; data.m_info.facialHairStyleID = display->m_characterInfo.facialHairStyleID;
data.m_info.faceID = display->m_characterInfo.faceID; data.m_info.faceID = display->m_characterInfo.faceID;
CCharacterCreation::InitCharacterComponent(&data, 0); CCharacterCreation::InitCharacterComponent(&data, 0);
@ -424,7 +426,52 @@ void CCharacterCreation::SetCharFacing(float facing) {
} }
void CCharacterCreation::CreateCharacter(const char* name) { void CCharacterCreation::CreateCharacter(const char* name) {
// TODO uint64_t guid = 0;
CharacterSelectionDisplay* display = nullptr;
auto index = CCharacterCreation::m_existingCharacterIndex;
if (index >= 0) {
display = CCharacterSelection::GetCharacterDisplay(index);
if (!display) {
return;
}
guid = display->m_characterInfo.guid;
if (display->m_characterInfo.name) {
if (SStrCmpI(name, display->m_characterInfo.name, STORM_MAX_STR)) {
name = display->m_characterInfo.name;
}
}
}
auto validationResult = ClientServices::CharacterValidateName(name);
if (validationResult != CHAR_NAME_SUCCESS) {
auto token = ClientServices::GetErrorToken(validationResult);
auto text = FrameScript_GetText(token, -1, GENDER_NOT_APPLICABLE);
FrameScript_SignalEvent(3, "%s%s", "OKAY", text);
return;
}
const auto& info = CCharacterCreation::m_character->m_data.m_info;
CHARACTER_CREATE_INFO character;
SStrCopy(character.name, name, sizeof(character.name));
character.raceID = info.raceID;
character.classID = CCharacterCreation::m_selectedClassID;
character.sexID = info.sexID;
character.skinID = info.skinID;
character.hairColorID = info.hairColorID;
character.hairStyleID = info.hairStyleID;
character.facialHairStyleID = info.facialHairStyleID;
character.faceID = info.faceID;
character.outfitID = 0;
if (guid && display) {
// TODO
} else {
CGlueMgr::CreateCharacter(&character);
}
} }
void CCharacterCreation::SetToExistingCharacter(uint32_t index) { void CCharacterCreation::SetToExistingCharacter(uint32_t index) {

View File

@ -26,7 +26,7 @@ class CCharacterCreation {
// Static variables // Static variables
static int32_t m_selectedClassID; static int32_t m_selectedClassID;
static int32_t m_existingCharacterIndex; static int32_t m_existingCharacterIndex;
static CHARACTER_CREATE_INFO* m_charPreferences[44]; static CHARACTER_PREFERENCES* m_charPreferences[44];
static int32_t m_raceIndex; static int32_t m_raceIndex;
static CSimpleModelFFX* m_charCustomizeFrame; static CSimpleModelFFX* m_charCustomizeFrame;
static float m_charFacing; static float m_charFacing;

View File

@ -1092,6 +1092,20 @@ void CGlueMgr::DeleteCharacter(uint64_t guid) {
ClientServices::CharacterDelete(guid); ClientServices::CharacterDelete(guid);
} }
void CGlueMgr::CreateCharacter(const CHARACTER_CREATE_INFO* character) {
if (!character) {
return;
}
CGlueMgr::m_idleState = IDLE_CREATE_CHARACTER;
CGlueMgr::m_showedDisconnect = 0;
auto errorText = ClientServices::GetErrorToken(46);
auto text = FrameScript_GetText(errorText, -1, GENDER_NOT_APPLICABLE);
FrameScript_SignalEvent(3u, "%s%s", "CANCEL", text);
ClientServices::RequestCharacterCreate(character);
}
void CGlueMgr::PollEnterWorld() { void CGlueMgr::PollEnterWorld() {
//if (!LoadingScreenDrawing()) //if (!LoadingScreenDrawing())
// return; // return;

View File

@ -104,6 +104,7 @@ class CGlueMgr {
static void UpdateCurrentScreen(const char* screen); static void UpdateCurrentScreen(const char* screen);
static bool HandleBattlenetDisconnect(); static bool HandleBattlenetDisconnect();
static void DeleteCharacter(uint64_t guid); static void DeleteCharacter(uint64_t guid);
static void CreateCharacter(const CHARACTER_CREATE_INFO* character);
static void PollEnterWorld(); static void PollEnterWorld();

View File

@ -1218,6 +1218,34 @@ enum WOWCS_OPS {
COP_WAIT_QUEUE = 10, COP_WAIT_QUEUE = 10,
}; };
enum VALIDATE_NAME_RESULT {
NAME_SUCCESS = 0,
NAME_FAILURE,
NAME_NO_NAME,
NAME_TOO_SHORT,
NAME_TOO_LONG,
NAME_INVALID_CHARACTER,
NAME_MIXED_LANGUAGES,
NAME_PROFANE,
NAME_RESERVED,
NAME_INVALID_APOSTROPHE,
NAME_MULTIPLE_APOSTROPHES,
NAME_THREE_CONSECUTIVE,
NAME_INVALID_SPACE,
NAME_CONSECUTIVE_SPACES,
NAME_RUSSIAN_CONSECUTIVE_SILENT_CHARACTERS,
NAME_RUSSIAN_SILENT_CHARACTER_AT_BEGINNING_OR_END,
NAME_DECLENSION_DOESNT_MATCH_BASE_NAME,
NUM_NAME_RESULTS,
};
enum CHAR_NAME_RESULT {
CHAR_NAME_RESULT_START = 87,
CHAR_NAME_SUCCESS = 87,
CHAR_NAME_NO_NAME = 89,
LAST_CHAR_NAME_RESULT = 103,
};
struct LoginData { struct LoginData {
char m_account[1280]; char m_account[1280];
int32_t m_loginServerID; int32_t m_loginServerID;
@ -1286,17 +1314,30 @@ struct CHARACTER_INFO {
uint8_t firstLogin; uint8_t firstLogin;
}; };
struct CHARACTER_CREATE_INFO { struct CHARACTER_PREFERENCES {
uint32_t raceID; uint32_t raceID;
uint32_t sexID; uint32_t sexID;
uint32_t classID; uint32_t classID;
uint32_t hairColorID; uint32_t hairColorID;
uint32_t skinID; uint32_t skinID;
uint32_t faceID; uint32_t faceID;
uint32_t facialFeatureID; uint32_t facialHairStyleID;
uint32_t hairStyleID; uint32_t hairStyleID;
}; };
struct CHARACTER_CREATE_INFO {
char name[48];
uint8_t raceID;
uint8_t classID;
uint8_t sexID;
uint8_t skinID;
uint8_t faceID;
uint8_t hairStyleID;
uint8_t hairColorID;
uint8_t facialHairStyleID;
uint8_t outfitID;
};
struct CLIENT_NETSTATS { struct CLIENT_NETSTATS {
uint32_t bytesSent; uint32_t bytesSent;
uint32_t messagesSent; uint32_t messagesSent;

View File

@ -65,6 +65,28 @@ void ClientConnection::DeleteCharacter(uint64_t guid) {
} }
} }
void ClientConnection::CharacterCreate(const CHARACTER_CREATE_INFO* info) {
this->Initiate(COP_CREATE_CHARACTER, 46, nullptr);
if (this->m_connected) {
CDataStore msg;
msg.Put(static_cast<uint32_t>(CMSG_CREATE_CHARACTER));
msg.PutString(info->name);
msg.Put(info->raceID);
msg.Put(info->classID);
msg.Put(info->sexID);
msg.Put(info->skinID);
msg.Put(info->faceID);
msg.Put(info->hairStyleID);
msg.Put(info->hairColorID);
msg.Put(info->facialHairStyleID);
msg.Put(info->outfitID);
msg.Finalize();
this->Send(&msg);
} else {
this->Cancel(4);
}
}
void ClientConnection::Cancel(int32_t errorCode) { void ClientConnection::Cancel(int32_t errorCode) {
this->Complete(0, errorCode); this->Complete(0, errorCode);
} }

View File

@ -31,6 +31,7 @@ class ClientConnection : public RealmConnection {
void EnumerateCharacters(ENUMERATE_CHARACTERS_CALLBACK fcn, void* param); void EnumerateCharacters(ENUMERATE_CHARACTERS_CALLBACK fcn, void* param);
void CharacterLogin(uint64_t id); void CharacterLogin(uint64_t id);
void DeleteCharacter(uint64_t guid); void DeleteCharacter(uint64_t guid);
void CharacterCreate(const CHARACTER_CREATE_INFO* info);
void Cancel(int32_t errorCode); void Cancel(int32_t errorCode);
void Cleanup(); void Cleanup();
void Connect(); void Connect();

View File

@ -20,7 +20,7 @@ int32_t RealmConnection::MessageHandler(void* param, NETMESSAGE msgId, uint32_t
} }
case SMSG_CREATE_CHAR: { case SMSG_CREATE_CHAR: {
// TODO result = connection->HandleCharacterCreate(msgId, time, msg);
break; break;
} }
@ -279,6 +279,13 @@ int32_t RealmConnection::HandleCharacterDelete(uint32_t msgId, uint32_t time, CD
return 1; return 1;
} }
int32_t RealmConnection::HandleCharacterCreate(uint32_t msgId, uint32_t time, CDataStore* msg) {
uint8_t result;
msg->Get(result);
static_cast<ClientConnection*>(this)->Complete(1, result);
return 1;
}
void RealmConnection::SetSelectedRealm(uint32_t a2, uint32_t a3, uint32_t a4) { void RealmConnection::SetSelectedRealm(uint32_t a2, uint32_t a3, uint32_t a4) {
// TODO // TODO
} }

View File

@ -42,6 +42,7 @@ class RealmConnection : public NetClient {
int32_t HandleAuthResponse(uint32_t msgId, uint32_t time, CDataStore* msg); int32_t HandleAuthResponse(uint32_t msgId, uint32_t time, CDataStore* msg);
int32_t HandleCharEnum(uint32_t msgId, uint32_t time, CDataStore* msg); int32_t HandleCharEnum(uint32_t msgId, uint32_t time, CDataStore* msg);
int32_t HandleCharacterDelete(uint32_t msgId, uint32_t time, CDataStore* msg); int32_t HandleCharacterDelete(uint32_t msgId, uint32_t time, CDataStore* msg);
int32_t HandleCharacterCreate(uint32_t msgId, uint32_t time, CDataStore* msg);
void SetSelectedRealm(uint32_t a2, uint32_t a3, uint32_t a4); void SetSelectedRealm(uint32_t a2, uint32_t a3, uint32_t a4);
void RequestCharacterEnum(); void RequestCharacterEnum();
void RequestCharacterLogin(uint64_t id); void RequestCharacterLogin(uint64_t id);