하아찡
[C++] 인벤토리 - 3 (장비 장착) 본문
언리얼 버전 5.5.3
서버언어 C++로 구성했습니다.
실행화면
코드를 보시기전 DB 테이블 구성을 먼저 보겠습니다. 간단하게 작업을 했습니다.
CREATE TABLE `equipment` (
`character_id` INT(10) NOT NULL,
`slot_name` VARCHAR(20) NOT NULL COLLATE 'utf8mb4_0900_ai_ci',
`instance_id` INT(10) NOT NULL,
PRIMARY KEY (`character_id`, `slot_name`) USING BTREE
)
어떤 캐릭터가 어떤 장비 슬롯장비를 장착했는지와 아이템DB 고유값을 저장해줍니다.
아이템 코드를 저장하지 않은 이유는 나중에 장비강화 기능이 추가됐을때 장비 강화 수치등 여러 옵션을 가져오기위해선 아이템 코드보단 해당 아이템 고유DBID를 가져오는게 맞다고 생각해서 작업했습니다.
그래서 코드에선 데이터를 가져올때 인벤토리 데이터랑 같이 join해서 값을 가져옵니다.
그러면 이제 장비 클래스 코드를 살펴봅시다.
PlayerEquiment.h
#pragma once
struct EquCodes {
int32 code = 0;
uint64 instanceID = 0;
};
class PlayerEquiment
{
public:
PlayerEquiment();
public:
bool GetLoadEquipment(){ return bLoad;} // 장비를 불러왔는지?
int GetAttack(){ return AddAttack;}
int GetDefense(){ return AddDefense;}
int GetSpeed() { return AddSpeed; }
float GetCriticalRate() { return AddCriticalRate; }
float GetCriticalChance() { return AddCriticalChance; }
int GetHp(){return AddHP;}
void PrintEquiments(); // 장착중인 장비 확인
bool SetEquimentItem(string slotName, uint64 instanceID, int code); // 장비 장착 및 해제
void LoadEquipment(Protocol::S_DB_LOADEQUIPMENT& pkt); // 첫 장비 로드
vector<Protocol::ItemData> GetEquipments(); // 장착중인 장비 목록
bool IsEquiment(string slotName); // 장비 장착여부
void DBSendEquipment(uint64 characterID); // DB서버에 저장
Protocol::S_LOADEQUIPMENT ClientSendEquipmentPkt(); // 현재 장착중인 장비데이터를 패킷화
private:
unordered_map<string, Protocol::ItemData> equipments;
int AddAttack = 0; // 장착으로 증가한 공격력
int AddDefense = 0; // 장착으로 증가한 방어력
int AddSpeed = 0; // 장착으로 증가한 스피드
int AddHP = 0; // 장착으로 증가한 체력
float AddCriticalRate = 0.0f; // 장착으로 증가한 치피증
float AddCriticalChance = 0.0f; // 장착으로 증가한 크리티컬확률
bool bLoad = false;
};
PlayerEquiment.cpp
#include "pch.h"
#include "PlayerEquiment.h"
#include "ItemUtils.h"
#include "CharacterDB.h"
PlayerEquiment::PlayerEquiment()
{
equipments["head"] = Protocol::ItemData();
equipments["weapon"] = Protocol::ItemData();
}
void PlayerEquiment::PrintEquiments()
{
if (equipments["weapon"].code() != 0) {
ServerItemData* item = ItemUtils::GetItem(equipments["weapon"].code());
LOG("Weapon : " << item->Name << " Attack : " << item->Attack);
}
else { LOG("Weapon : Not Equ"); }
if (equipments["head"].code() != 0) {
ServerItemData* item = ItemUtils::GetItem(equipments["head"].code());
LOG("Head : " << item->Name << " Defense : " << item->Defense);
}
else { LOG("Not Equ"); }
}
bool PlayerEquiment::SetEquimentItem(string slotName, uint64 instanceID, int code)
{
if(!IsEquiment(slotName)) return false;
if (code == 0) {
equipments[slotName].set_code(0);
return false;
}
ServerItemData* item = ItemUtils::GetItem(code);
if (item == nullptr) {
LOG("잘못된 아이템정보");
return false;
}
if (equipments[slotName].code() != 0) {
// TODO 기존 장착 옵션 제거
ServerItemData* item = ItemUtils::GetItem(equipments[slotName].code());
AddAttack -= item->Attack;
AddDefense -= item->Defense;
AddCriticalRate -= item->CriticalRate;
AddCriticalChance -= item->Critical;
AddHP -= item->Hp;
}
if (equipments[slotName].id() == instanceID) {
// 동일 장비 장착 해제
equipments[slotName].set_code(0);
equipments[slotName].set_id(0);
return true;
}
//장비장착
equipments[slotName].set_code(code);
equipments[slotName].set_id(instanceID);
AddAttack += item->Attack;
AddDefense += item->Defense;
AddCriticalRate += item->CriticalRate;
AddCriticalChance += item->Critical;
AddHP += item->Hp;
return true;
}
void PlayerEquiment::LoadEquipment(Protocol::S_DB_LOADEQUIPMENT& pkt)
{
for (auto item : pkt.items()) {
SetEquimentItem(item.slot_name(),item.id(), item.code());
}
bLoad = true;
}
vector<Protocol::ItemData> PlayerEquiment::GetEquipments()
{
vector<Protocol::ItemData> items;
for (auto& item : equipments) {
if (item.second.id() != 0) {
items.push_back(item.second);
}
}
return items;
}
bool PlayerEquiment::IsEquiment(string slotName)
{
if (equipments.find(slotName) == equipments.end()) {
LOG("잘못된 슬롯정보");
return false;
}
else {
return true;
}
}
void PlayerEquiment::DBSendEquipment(uint64 characterID)
{
Protocol::C_DB_EQUIPMENT pkt;
pkt.set_characterid(characterID);
for (auto equ : equipments) {
Protocol::Equipment* item = pkt.add_items();
item->set_character_id(characterID);
item->set_code(equ.second.id());
item->set_slot_name(equ.first);
}
CharacterDB::UpdatePlayerEquipment(pkt);
}
Protocol::S_LOADEQUIPMENT PlayerEquiment::ClientSendEquipmentPkt()
{
Protocol::S_LOADEQUIPMENT pkt;
for (auto equ : equipments) {
Protocol::ItemData* item = pkt.add_items();
item->CopyFrom(equ.second);
}
return pkt;
}
DB서버에서 데이터를 추출해서 가져오는 함수입니다.
DBClientPacketHandler.cpp (장착중인 장비 로드)
bool Handle_C_DB_LOADINVENTORY(PacketSessionRef& session, Protocol::C_DB_LOADINVENTORY& pkt)
{
//플레이어 접속할때 인벤토리 요청 처리
Protocol::S_DB_LOADINVENTORY sendPkt;
DBConnection* dbConn = GDBConnectionPool->Pop();
dbConn->Unbind();
DBBind<1, 7> dbBind(*dbConn, "SELECT id, slot_index, item_id, quantity, enhancement_level, durability, is_equipped FROM inventory WHERE character_id = ?");
int64 DBID = pkt.characterid();
dbBind.BindParam(0, DBID);
int32 out_id;
int32 out_slotindex;
int32 out_itemcode;
int32 out_quanty;
int32 out_enhancement;
int32 out_durability;
bool out_equipped;
dbBind.BindCol(0, OUT out_id);
dbBind.BindCol(1, OUT out_slotindex);
dbBind.BindCol(2, OUT out_itemcode);
dbBind.BindCol(3, OUT out_quanty);
dbBind.BindCol(4, OUT out_enhancement);
dbBind.BindCol(5, OUT out_durability);
dbBind.BindCol(6, OUT out_equipped);
ASSERT_CRASH(dbBind.Execute());
while (dbConn->Fetch())
{
sendPkt.set_requestid(pkt.requestid());
Protocol::ItemData* item = sendPkt.add_items();
item->set_id(out_id);
item->set_slot_index(out_slotindex);
item->set_code(out_itemcode);
item->set_quantity(out_quanty);
item->set_enhancement_level(out_enhancement);
item->set_durability(out_durability);
item->set_is_equipped(out_equipped);
}
GDBConnectionPool->Push(dbConn);
SendBufferRef sendBuffer = DBClientPacketHandler::MakeSendBuffer(sendPkt);
session->Send(sendBuffer);
LOG("SendInventory");
return false;
}
DBClientPacketHandler.cpp (장착여부 저장)
bool Handle_C_DB_EQUIPMENT(PacketSessionRef& session, Protocol::C_DB_EQUIPMENT& pkt)
{
// 장비 장착여부 DB에 저장
int64 DBID = pkt.characterid();
string Query =
"CALL UpdateOrInsertEquipment(?, ?, ?);";
DBConnection* dbConn = GDBConnectionPool->Pop();
for (auto item : pkt.items()) {
dbConn->Unbind();
DBBind<3, 0> dbBind(*dbConn, Query);
std::vector<char> bufferSlotName(item.slot_name().begin(), item.slot_name().end());
bufferSlotName.push_back('\0'); // NULL 종료 추가
int64 instanceID = item.code();
dbBind.BindParam(0, DBID);
dbBind.BindParam(1, bufferSlotName.data(), bufferSlotName.size());
dbBind.BindParam(2, instanceID);
ASSERT_CRASH(dbBind.Execute());
}
GDBConnectionPool->Push(dbConn);
return false;
}
장비장착 저장 DB프로시저
BEGIN
DECLARE existing_count INT;
-- 존재 여부 확인
SELECT COUNT(*) INTO existing_count
FROM equipment
WHERE character_id = p_character_id AND slot_name = p_slot_name;
IF existing_count = 0 THEN
-- 데이터가 없고 instance_id가 0이 아니면 INSERT
IF p_instance_id != 0 THEN
INSERT INTO equipment (character_id, slot_name, instance_id)
VALUES (p_character_id, p_slot_name, p_instance_id);
END IF;
ELSE
-- 데이터가 존재함
IF p_instance_id = 0 THEN
DELETE FROM equipment
WHERE character_id = p_character_id AND slot_name = p_slot_name;
ELSE
UPDATE equipment
SET instance_id = p_instance_id
WHERE character_id = p_character_id AND slot_name = p_slot_name;
END IF;
END IF;
END
장비 장착에 대한 저장은 프로시저로 사용했습니다.
이전에 장비를 장착하고있다가 현재 저장할때 데이터가없으면 기존 데이터를 삭제하고,
데이터가있으면 업데이트하고,
데이터 자체가없다면 추가하는 방식을 한번에 처리하기위해 프로시저로 작업을 해두었습니다.
반응형
'C++ > 이것저것서버테스트' 카테고리의 다른 글
[C++] 몬스터 스포너 xml (0) | 2025.03.27 |
---|---|
[C++] 개인맵 및 제한시간 (0) | 2025.03.27 |
[C++] 인벤토리 - 2 (인벤토리 서버저장) (0) | 2025.03.25 |
[C++] 인벤토리 (0) | 2025.03.22 |
[C++] 서버에서 아이템 드랍 처리 (0) | 2025.03.22 |