하아찡

[C++] 인벤토리 - 3 (장비 장착) 본문

C++/이것저것서버테스트

[C++] 인벤토리 - 3 (장비 장착)

하아찡 2025. 3. 25. 13:59

언리얼 버전 5.5.3

서버언어 C++로 구성했습니다.


실행화면

아이템 옵션적용

 

캐릭터 접속시 장착된 장비옵션적용
DB에 저장된 데이터

 


코드를 보시기전 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

 

장비 장착에 대한 저장은 프로시저로 사용했습니다.

이전에 장비를 장착하고있다가 현재 저장할때 데이터가없으면 기존 데이터를 삭제하고,

데이터가있으면 업데이트하고, 

데이터 자체가없다면 추가하는 방식을 한번에 처리하기위해 프로시저로 작업을 해두었습니다.

 

 

반응형