feat: c++ pragma 방식 => protocol buffer로 직렬화 수정 및 클라이언트 python gui 앱으로 변경

This commit is contained in:
bumpsoo 2026-02-08 14:59:16 +09:00
parent a88b22b177
commit cd192d4ec4
14 changed files with 305 additions and 371 deletions

View file

@ -9,10 +9,17 @@ add_compile_options(-Wall -Wextra -Wpedantic)
# Boost 및 의존성 찾기
find_package(Boost REQUIRED COMPONENTS system)
find_package(OpenSSL REQUIRED)
find_package(Protobuf REQUIRED)
# 컴파일 명령 추출 활성화 (LSP/IDE 용)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# proto 컴파일
file(GLOB PROTO_FILES "${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto")
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_FILES})
add_custom_target(generate_proto DEPENDS ${PROTO_SRCS} ${PROTO_HDRS})
add_executable(Server
server/main.cpp
server/NetworkService.cpp
@ -20,14 +27,14 @@ add_executable(Server
server/PacketHandler.cpp
server/SessionManager.cpp
server/DatabaseManager.cpp
${PROTO_SRCS} ${PROTO_HDRS}
)
target_include_directories(Server PRIVATE include)
target_link_libraries(Server PRIVATE Boost::system OpenSSL::SSL OpenSSL::Crypto)
add_executable(Client
client/Client.cpp
client/main.cpp
target_include_directories(Server PRIVATE include ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(Server PRIVATE
Boost::system
OpenSSL::SSL
OpenSSL::Crypto
protobuf::libprotobuf
)
target_include_directories(Client PRIVATE include)
target_link_libraries(Client PRIVATE Boost::system OpenSSL::SSL OpenSSL::Crypto)

View file

@ -5,7 +5,8 @@ RUN apt-get update && apt-get install -y \
make \
g++ \
libboost-all-dev \
libssl-dev
libssl-dev \
protobuf-compiler
COPY . /app
WORKDIR /app

3
client/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
Protocol_pb2.py
venv
__pycache__

View file

@ -1,233 +0,0 @@
#include "Client.h"
#include <iostream>
#include <thread>
BaseClient::BaseClient(boost::asio::io_context &io_context,
const std::string &host, const std::string &port)
: socket_(io_context) {
tcp::resolver resolver(io_context);
boost::asio::connect(socket_, resolver.resolve(host, port));
}
void BaseClient::SendPacket(PacketID id, const void *payload,
size_t payloadSize) {
PacketHeader header;
header.id = static_cast<uint16_t>(id);
header.size = static_cast<uint16_t>(payloadSize);
std::vector<uint8_t> buffer(sizeof(PacketHeader) + payloadSize);
std::memcpy(buffer.data(), &header, sizeof(PacketHeader));
if (payload && payloadSize > 0) {
std::memcpy(buffer.data() + sizeof(PacketHeader), payload, payloadSize);
}
boost::asio::write(socket_, boost::asio::buffer(buffer));
}
bool BaseClient::ReceiveHeader(PacketHeader &header) {
try {
boost::asio::read(socket_, boost::asio::buffer(&header, sizeof(header)));
return true;
} catch (...) {
return false;
}
}
bool BaseClient::Login(const std::string &nickname) {
PKT_CS_Login loginPkt;
std::memset(loginPkt.nickname, 0, sizeof(loginPkt.nickname));
std::strncpy(loginPkt.nickname, nickname.c_str(),
sizeof(loginPkt.nickname) - 1);
SendPacket(PacketID::CS_Login, &loginPkt, sizeof(loginPkt));
std::cout << "로그인 요청 보냄: " << nickname << std::endl;
// 로그인 결과 대기
PacketHeader header;
if (!ReceiveHeader(header) ||
header.id != static_cast<uint16_t>(PacketID::SC_LoginResult)) {
std::cerr << "로그인 응답을 받지 못했습니다." << std::endl;
return false;
}
PKT_SC_LoginResult res;
if (!ReceivePayload(res)) {
std::cerr << "로그인 응답 수신 실패" << std::endl;
return false;
}
if (res.result == 1) {
std::cout << "로그인 성공!" << std::endl;
return true;
} else if (res.result == 0) {
std::cerr << "로그인 실패: 이미 접속 중인 유저입니다." << std::endl;
return false;
} else {
std::cerr << "로그인 실패: 알 수 없는 오류" << std::endl;
return false;
}
}
void BaseClient::StartReceive() {
receiveThread_ = std::jthread(&BaseClient::ReceiveLoop, this);
}
void BaseClient::StopReceive() {
boost::system::error_code ec;
socket_.shutdown(tcp::socket::shutdown_both, ec);
socket_.close(ec);
receiveThread_.request_stop();
}
void BaseClient::ReceiveLoop(std::stop_token stopToken) {
try {
while (!stopToken.stop_requested()) {
PacketHeader header;
if (!ReceiveHeader(header))
break;
HandlePacket(header);
}
} catch (...) {
}
}
void BaseClient::HandlePacket(const PacketHeader &header) {
PacketID id = static_cast<PacketID>(header.id);
switch (id) {
case PacketID::Chat: {
std::vector<char> buffer(header.size + 1, 0);
boost::asio::read(socket_, boost::asio::buffer(buffer.data(), header.size));
std::cout << "\n" << buffer.data() << std::endl;
} break;
case PacketID::SC_UpgradeResult:
HandleUpgradeResult();
break;
case PacketID::SC_SellResult:
HandleSellResult();
break;
default:
// 처리하지 않는 패킷은 페이로드만 건너뜀
if (header.size > 0) {
std::vector<uint8_t> dummy(header.size);
boost::asio::read(socket_,
boost::asio::buffer(dummy.data(), header.size));
}
break;
}
}
void BaseClient::HandleUpgradeResult() {
PKT_SC_UpgradeResult res;
if (ReceivePayload(res)) {
currentLevel_ = res.currentLevel;
currentGold_ = res.currentGold;
// 강화 결과 출력은 채팅으로
}
}
void BaseClient::HandleSellResult() {
PKT_SC_SellResult res;
if (ReceivePayload(res)) {
currentLevel_ = 0;
currentGold_ = res.totalGold;
// 판매 결과는 본인에게만
std::cout << "\n>> 판매 완료: " << res.earnedGold << " 골드 획득!"
<< std::endl;
}
}
void InteractiveClient::Run() {
std::cout << "닉네임을 입력하세요: ";
std::cin >> nickname_;
if (!Login(nickname_)) {
std::cerr << "로그인에 실패하여 종료합니다." << std::endl;
return;
}
// 로그인 후 비동기 수신 시작
StartReceive();
while (true) {
if (currentLevel_ == 0) {
std::cout << "\n[현재 검 없음, 골드: " << currentGold_
<< "] 1. 검 구매 시도 (1000골드), 2. 종료\n입력: ";
} else {
std::cout << "\n[현재 검 레벨: " << currentLevel_
<< ", 골드: " << currentGold_
<< "] 1. 강화 시도, 2. 판매, 3. 종료\n입력: ";
}
int choice;
if (!(std::cin >> choice))
break;
if (currentLevel_ == 0) {
if (choice == 1) {
SendPacket(PacketID::CS_UpgradeSword);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else if (choice == 2) {
break;
}
} else {
if (choice == 1) {
SendPacket(PacketID::CS_UpgradeSword);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else if (choice == 2) {
SendPacket(PacketID::CS_SellSword);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else if (choice == 3) {
break;
}
}
}
StopReceive();
}
#include <fstream>
void ScenarioClient::Run() {
std::ifstream is(scenarioFile_);
if (!is.is_open()) {
std::cerr << "시나리오 파일을 열 수 없습니다: " << scenarioFile_
<< std::endl;
return;
}
if (!(is >> nickname_))
return;
if (!Login(nickname_)) {
std::cerr << "로그인에 실패하여 종료합니다." << std::endl;
return;
}
// 비동기 수신 시작
StartReceive();
std::string line;
while (is >> line) {
int choice = std::stoi(line);
std::cout << "\n시나리오 입력 실행: " << choice << std::endl;
if (currentLevel_ == 0) {
if (choice == 1) {
SendPacket(PacketID::CS_UpgradeSword);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else if (choice == 2) {
break;
}
} else {
if (choice == 1) {
SendPacket(PacketID::CS_UpgradeSword);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else if (choice == 2) {
SendPacket(PacketID::CS_SellSword);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else if (choice == 3) {
break;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
StopReceive();
}

19
client/README.md Normal file
View file

@ -0,0 +1,19 @@
## c++ cli 클라이언트 대신하는 python gui client
- tkinter 사용
- protocol buffer 사용
## protocol buffer 빌드
```bash
protoc --python_out=. Protocol.proto -I ../proto
```
## python 설치 및 실행
```bash
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python main.py
```

View file

@ -1,44 +0,0 @@
#include "Client.h"
#include <iostream>
int main(int argc, char *argv[]) {
bool isScenario = false;
std::string nickname;
std::string scenarioFile;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--scenario" && i + 1 < argc) {
isScenario = true;
scenarioFile = argv[++i];
} else {
nickname = arg;
}
}
try {
boost::asio::io_context io_context;
const char *env_host = std::getenv("SERVER_HOST");
const char *env_port = std::getenv("SERVER_PORT");
std::string host = env_host ? env_host : "127.0.0.1";
std::string port = env_port ? env_port : "30000";
if (isScenario) {
if (scenarioFile.empty()) {
std::cerr
<< "시나리오 파일명이 필요합니다. 사용법: --scenario <파일경로>"
<< std::endl;
return 1;
}
ScenarioClient client(io_context, host, port, scenarioFile);
client.Run();
} else {
InteractiveClient client(io_context, host, port);
client.Run();
}
} catch (std::exception &e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}

196
client/main.py Normal file
View file

@ -0,0 +1,196 @@
import socket
import threading
import struct
import customtkinter as ctk
from enum import IntEnum
import Protocol_pb2 as Protocol
class PacketID(IntEnum):
Ping = 1
CS_Login = 10
SC_LoginResult = 11
Chat = 20
CS_UpgradeSword = 30
SC_UpgradeResult = 31
CS_SellSword = 35
SC_SellResult = 36
CS_RankingRequest = 40
SC_RankingList = 41
class SwordGameClient(ctk.CTk):
def __init__(self):
super().__init__()
self.title("C++ Sword Game - Python Client")
self.geometry("1200x800")
ctk.set_appearance_mode("dark")
# 네트워크 관련
self.socket = None
self.connected = False
self.nickname = ""
# 게임 정보
self.current_level = 0
self.current_gold = 0
# UI 초기화
self.setup_login_ui()
def setup_login_ui(self):
self.login_frame = ctk.CTkFrame(self)
self.login_frame.pack(expand=True)
self.label = ctk.CTkLabel(self.login_frame, text="검 키우기 온라인", font=("Arial", 24, "bold"))
self.label.pack(pady=20)
self.nickname_entry = ctk.CTkEntry(self.login_frame, placeholder_text="닉네임 입력")
self.nickname_entry.pack(pady=10)
self.login_button = ctk.CTkButton(self.login_frame, text="접속", command=self.connect_to_server)
self.login_button.pack(pady=20)
def setup_game_ui(self):
for widget in self.winfo_children():
widget.destroy()
# 메인 레이아웃
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=2)
self.grid_rowconfigure(0, weight=1)
# 왼쪽: 유저 정보 창
self.info_frame = ctk.CTkFrame(self)
self.info_frame.grid(row=0, column=0, padx=20, pady=20, sticky="nsew")
self.user_label = ctk.CTkLabel(self.info_frame, text=f"플레이어: {self.nickname}", font=("Arial", 16))
self.user_label.pack(pady=10)
self.gold_label = ctk.CTkLabel(self.info_frame, text="골드: 0", font=("Arial", 18, "bold"), text_color="#FFD700")
self.gold_label.pack(pady=5)
self.level_label = ctk.CTkLabel(self.info_frame, text="검 레벨: 0", font=("Arial", 20, "bold"))
self.level_label.pack(pady=10)
self.upgrade_btn = ctk.CTkButton(self.info_frame, text="강화 시도", fg_color="#28a745", hover_color="#218838", command=self.send_upgrade)
self.upgrade_btn.pack(pady=10, fill="x", padx=20)
self.sell_btn = ctk.CTkButton(self.info_frame, text="판매 하기", fg_color="#dc3545", hover_color="#c82333", command=self.send_sell)
self.sell_btn.pack(pady=10, fill="x", padx=20)
# 오른쪽: 채팅 창
self.chat_frame = ctk.CTkFrame(self)
self.chat_frame.grid(row=0, column=1, padx=20, pady=20, sticky="nsew")
self.chat_box = ctk.CTkTextbox(self.chat_frame, state="disabled")
self.chat_box.pack(expand=True, fill="both", padx=10, pady=10)
self.chat_entry = ctk.CTkEntry(self.chat_frame, placeholder_text="메시지 입력...")
self.chat_entry.pack(fill="x", padx=10, pady=(0, 10))
self.chat_entry.bind("<Return>", lambda e: self.send_chat())
# TODO: 채팅창 오른 쪽에 랭킹?
def connect_to_server(self):
self.nickname = self.nickname_entry.get()
if not self.nickname:
return
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect(("127.0.0.1", 30000))
self.connected = True
threading.Thread(target=self.receive_loop, daemon=True).start()
pkt = Protocol.CS_Login()
pkt.nickname = self.nickname
self.send_packet(PacketID.CS_Login, pkt)
except Exception as e:
print(f"Connection failed: {e}")
self.nickname_entry.configure(placeholder_text="연결 실패!")
def send_packet(self, packet_id, protobuf_obj=None):
if not self.socket: return
payload = b""
if protobuf_obj:
payload = protobuf_obj.SerializeToString()
# Header: size (2 bytes) + id (2 bytes), Little Endian
header = struct.pack('<HH', len(payload), int(packet_id))
self.socket.sendall(header + payload)
def receive_loop(self):
try:
while self.connected:
header_data = self.socket.recv(4)
if not header_data: break
size, pkt_id = struct.unpack('<HH', header_data)
payload = b""
if size > 0:
payload = self.socket.recv(size)
self.handle_packet(pkt_id, payload)
except Exception as e:
print(f"Receive error: {e}")
finally:
self.connected = False
def handle_packet(self, pkt_id, payload):
if pkt_id == PacketID.SC_LoginResult:
res = Protocol.SC_LoginResult()
res.ParseFromString(payload)
if res.success:
self.after(0, self.setup_game_ui)
else:
print("Login Failed")
elif pkt_id == PacketID.SC_UpgradeResult:
res = Protocol.SC_UpgradeResult()
res.ParseFromString(payload)
self.current_level = res.current_level
self.current_gold = res.current_gold
self.after(0, self.update_stats)
elif pkt_id == PacketID.SC_SellResult:
res = Protocol.SC_SellResult()
res.ParseFromString(payload)
self.current_level = 0
self.current_gold = res.total_gold
self.after(0, self.update_stats)
self.after(0, lambda: self.log_chat(f"[시스템] 검 판매 완료! {res.earned_gold} 골드 획득."))
elif pkt_id == PacketID.Chat:
msg = payload.decode('utf-8')
self.after(0, lambda: self.log_chat(msg))
def update_stats(self):
self.level_label.configure(text=f"검 레벨: {self.current_level}")
self.gold_label.configure(text=f"골드: {self.current_gold:,}")
def send_upgrade(self):
self.send_packet(PacketID.CS_UpgradeSword)
def send_sell(self):
self.send_packet(PacketID.CS_SellSword)
def send_chat(self):
msg = self.chat_entry.get()
if msg:
header = struct.pack('<HH', len(msg.encode('utf-8')), int(PacketID.Chat))
self.socket.sendall(header + msg.encode('utf-8'))
self.chat_entry.delete(0, 'end')
def log_chat(self, msg):
self.chat_box.configure(state="normal")
self.chat_box.insert("end", msg + "\n")
self.chat_box.see("end")
self.chat_box.configure(state="disabled")
if __name__ == "__main__":
app = SwordGameClient()
app.mainloop()

4
client/requirements.txt Normal file
View file

@ -0,0 +1,4 @@
customtkinter==5.2.2
darkdetect==0.8.0
packaging==26.0
protobuf==6.33.5

View file

@ -1,11 +1,14 @@
#pragma once
#include "Packet.h"
#include <atomic>
#include <boost/asio.hpp>
#include <string>
#include <thread>
namespace google::protobuf {
class Message;
}
using boost::asio::ip::tcp;
class BaseClient {
@ -15,20 +18,10 @@ public:
virtual ~BaseClient() = default;
void SendPacket(PacketID id, const void *payload = nullptr,
size_t payloadSize = 0);
void SendPacket(PacketID id, const google::protobuf::Message *pkt = nullptr);
bool ReceiveHeader(PacketHeader &header);
template <typename T> bool ReceivePayload(T &payload) {
try {
boost::asio::read(socket_, boost::asio::buffer(&payload, sizeof(T)));
return true;
} catch (...) {
return false;
}
}
bool Login(const std::string &nickname);
// 비동기 수신 시작
@ -48,8 +41,8 @@ protected:
void ReceiveLoop(std::stop_token stopToken);
void HandlePacket(const PacketHeader &header);
void HandleUpgradeResult();
void HandleSellResult();
void HandleUpgradeResult(const PacketHeader &header);
void HandleSellResult(const PacketHeader &header);
};
class InteractiveClient : public BaseClient {

View file

@ -46,45 +46,3 @@ namespace GameConfig {
const uint32_t MAX_SWORD_LEVEL = 20;
const uint64_t INITIAL_GOLD = 10000;
} // namespace GameConfig
// 고정 크기 데이터 전송을 위한 구조체 정의
#pragma pack(push, 1)
struct PKT_CS_Login {
// 유저 닉네임 (최대 32자)
char nickname[32];
};
struct PKT_SC_LoginResult {
// 0: 이미 접속 중, 1: 성공
uint8_t result;
};
struct PKT_SC_UpgradeResult {
// 0: 파괴, 1: 성공, 2: 실패
uint8_t result;
// 현재 강화 레벨
uint32_t currentLevel;
// 현재 보유 골드
uint64_t currentGold;
};
struct PKT_SC_SellResult {
// 판매 후 획득 골드
uint64_t earnedGold;
// 현재 총 골드
uint64_t totalGold;
};
struct RankingEntry {
// 유저 이름 (최대 32자)
char username[32];
// 검 레벨
uint32_t swordLevel;
};
struct PKT_SC_RankingList {
// 랭킹 리스트 개수
uint32_t count;
// 이후 RankingEntry[count] 만큼 데이터가 따라옴
};
#pragma pack(pop)

View file

@ -16,14 +16,15 @@ public:
void Start();
void Send(std::span<const uint8_t> data);
template <typename T> void SendPacket(PacketID id, const T &payload) {
template <typename T> void SendPacket(PacketID id, const T &pkt) {
uint16_t size = static_cast<uint16_t>(pkt.ByteSizeLong());
PacketHeader header;
header.id = static_cast<uint16_t>(id);
header.size = sizeof(T);
header.size = size;
std::vector<uint8_t> buffer(sizeof(PacketHeader) + sizeof(T));
std::vector<uint8_t> buffer(sizeof(PacketHeader) + size);
std::memcpy(buffer.data(), &header, sizeof(PacketHeader));
std::memcpy(buffer.data() + sizeof(PacketHeader), &payload, sizeof(T));
pkt.SerializeToArray(buffer.data() + sizeof(PacketHeader), size);
Send(buffer);
}

31
proto/Protocol.proto Normal file
View file

@ -0,0 +1,31 @@
syntax = "proto3";
package Protocol;
message CS_Login {
string nickname = 1;
}
message SC_LoginResult {
bool success = 1;
}
message SC_UpgradeResult {
uint32 result = 1;
uint32 current_level = 2;
uint64 current_gold = 3;
}
message SC_SellResult {
uint64 earned_gold = 1;
uint64 total_gold = 2;
}
message RankingEntry {
string nickname = 1;
uint32 level = 2;
}
message SC_RankingList {
repeated RankingEntry entries = 1;
}

View file

@ -1,6 +1,7 @@
#include "PacketHandler.h"
#include "DatabaseManager.h"
#include "Logger.h"
#include "Protocol.pb.h"
#include "SessionManager.h"
#include "SwordLogic.h"
#include <boost/asio/use_awaitable.hpp>
@ -17,27 +18,25 @@ PacketHandler::HandlePacket(std::shared_ptr<Session> session,
} break;
case PacketID::CS_Login: {
if (packet.payload.size() < sizeof(PKT_CS_Login)) {
Logger::Log("로그인 패킷 크기가 올바르지 않습니다.");
Protocol::CS_Login pkt;
if (!pkt.ParseFromArray(packet.payload.data(), packet.payload.size())) {
Logger::Log("로그인 패킷 파싱 실패");
co_return;
}
const PKT_CS_Login *loginPkt =
reinterpret_cast<const PKT_CS_Login *>(packet.payload.data());
std::string nickname(loginPkt->nickname);
std::string nickname = pkt.nickname();
auto userData = co_await DatabaseManager::GetInstance().LoadUser(nickname);
// 골드와 검 레벨 설정 (닉네임은 TryJoin에서 설정)
session->SetGold(userData.gold);
session->SetSwordLevel(userData.swordLevel);
PKT_SC_LoginResult loginResult;
Protocol::SC_LoginResult loginResult;
// 중복 체크
if (!SessionManager::GetInstance().TryJoin(session, userData.nickname)) {
Logger::Log("중복 로그인 거부: ", nickname);
loginResult.result = 0;
loginResult.set_success(false);
session->SendPacket(PacketID::SC_LoginResult, loginResult);
co_return;
}
@ -46,7 +45,7 @@ PacketHandler::HandlePacket(std::shared_ptr<Session> session,
" (Gold: ", session->GetGold(),
", Level: ", session->GetSwordLevel(), ")");
loginResult.result = 1;
loginResult.set_success(true);
session->SendPacket(PacketID::SC_LoginResult, loginResult);
} break;
@ -55,14 +54,14 @@ PacketHandler::HandlePacket(std::shared_ptr<Session> session,
uint64_t cost = SwordLogic::GetUpgradeCost(currentLevel);
uint64_t currentGold = session->GetGold();
PKT_SC_UpgradeResult res;
Protocol::SC_UpgradeResult res;
if (currentGold < cost) {
// 골드 부족
res.result = 2;
res.set_result(2);
} else {
session->SetGold(currentGold - cost);
uint8_t result = SwordLogic::TryUpgrade(currentLevel);
res.result = result;
res.set_result(result);
// 성공
if (result == 1) {
@ -76,8 +75,8 @@ PacketHandler::HandlePacket(std::shared_ptr<Session> session,
session->GetNickname(), session->GetGold(), session->GetSwordLevel());
}
res.currentLevel = session->GetSwordLevel();
res.currentGold = session->GetGold();
res.set_current_level(session->GetSwordLevel());
res.set_current_gold(session->GetGold());
// 결과 패킷 전송
session->SendPacket(PacketID::SC_UpgradeResult, res);
@ -86,9 +85,9 @@ PacketHandler::HandlePacket(std::shared_ptr<Session> session,
if (currentGold >= cost) {
uint32_t attemptedLevel = currentLevel + 1;
std::string resultText;
if (res.result == 1) {
if (res.result() == 1) {
resultText = "를 달성했습니다!";
} else if (res.result == 0) {
} else if (res.result() == 0) {
resultText = " 시도 중 검이 파괴되었습니다!";
} else {
resultText = " 달성에 실패했습니다!";
@ -105,8 +104,9 @@ PacketHandler::HandlePacket(std::shared_ptr<Session> session,
std::vector<uint8_t> chatPayload(chatMsg.begin(), chatMsg.end());
SessionManager::GetInstance().Broadcast(chatHeader, chatPayload);
Logger::Log("강화 시도 [", session->GetNickname(), "]: ", (int)res.result,
" (레벨: ", currentLevel, "->", res.currentLevel, ")");
Logger::Log("강화 시도 [", session->GetNickname(),
"]: ", (int)res.result(), " (레벨: ", currentLevel, "->",
res.current_level(), ")");
}
} break;
@ -121,9 +121,9 @@ PacketHandler::HandlePacket(std::shared_ptr<Session> session,
co_await DatabaseManager::GetInstance().SaveUser(
session->GetNickname(), session->GetGold(), session->GetSwordLevel());
PKT_SC_SellResult res;
res.earnedGold = price;
res.totalGold = newGold;
Protocol::SC_SellResult res;
res.set_earned_gold(price);
res.set_total_gold(newGold);
session->SendPacket(PacketID::SC_SellResult, res);

View file

@ -2,17 +2,15 @@
set -e
echo "Starting project setup..."
if [ ! -d "build" ]; then
mkdir build
echo "Created build directory."
fi
cd build
cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
echo "CMake configuration complete."
cmake --build . --target generate_proto
cd ..
ln -sf build/compile_commands.json compile_commands.json
echo "완료"