tests: cover quest framing and restart cooldowns
Some checks failed
build / Linux asan (push) Has been cancelled
build / Linux release (push) Has been cancelled
build / FreeBSD build (push) Has been cancelled

This commit is contained in:
server
2026-04-14 14:38:21 +02:00
parent cf6deb1895
commit 44f9d92e8c
8 changed files with 185 additions and 58 deletions

View File

@@ -40,6 +40,7 @@
#include "xmas_event.h"
#include "banword.h"
#include "target.h"
#include "request_cooldown.h"
#include "wedding.h"
#include "mob_manager.h"
#include "mining.h"
@@ -5700,7 +5701,7 @@ void CHARACTER::ReqSafeboxLoad(const char* pszPassword)
int iPulse = thecore_pulse();
const int last_safebox_load_time = GetSafeboxLoadTime();
if (last_safebox_load_time > 0 && iPulse - last_safebox_load_time < PASSES_PER_SEC(10))
if (HasRecentRequestCooldown(last_safebox_load_time, iPulse, PASSES_PER_SEC(10)))
{
ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<창고> 창고를 닫은지 10초 안에는 열 수 없습니다."));
return;

View File

@@ -27,6 +27,7 @@
#include "unique_item.h"
#include "threeway_war.h"
#include "log.h"
#include "request_cooldown.h"
#include "common/VnumHelper.h"
extern int g_server_id;
@@ -946,7 +947,7 @@ ACMD(do_mall_password)
return;
}
if (last_mall_load_time > 0 && iPulse - last_mall_load_time < passes_per_sec * 10) // 10초에 한번만 요청 가능
if (HasRecentRequestCooldown(last_mall_load_time, iPulse, passes_per_sec * 10)) // 10초에 한번만 요청 가능
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<창고> 창고를 닫은지 10초 안에는 열 수 없습니다."));
return;

80
src/game/quest_packet.cpp Normal file
View File

@@ -0,0 +1,80 @@
#include "stdafx.h"
#include "common/tables.h"
#include "packet_structs.h"
#include "quest_packet.h"
#include <algorithm>
#include <array>
#include <cstring>
namespace quest
{
namespace
{
constexpr uint8_t QUEST_SEND_ISBEGIN_LOCAL = 1 << 0;
constexpr uint8_t QUEST_SEND_TITLE_LOCAL = 1 << 1;
constexpr uint8_t QUEST_SEND_CLOCK_NAME_LOCAL = 1 << 2;
constexpr uint8_t QUEST_SEND_CLOCK_VALUE_LOCAL = 1 << 3;
constexpr uint8_t QUEST_SEND_COUNTER_NAME_LOCAL = 1 << 4;
constexpr uint8_t QUEST_SEND_COUNTER_VALUE_LOCAL = 1 << 5;
constexpr uint8_t QUEST_SEND_ICON_FILE_LOCAL = 1 << 6;
void AppendBytes(std::vector<uint8_t>& packet, const void* data, size_t size)
{
const auto* bytes = static_cast<const uint8_t*>(data);
packet.insert(packet.end(), bytes, bytes + size);
}
template <size_t N>
void AppendFixedString(std::vector<uint8_t>& packet, const std::string& value)
{
std::array<char, N> field {};
const size_t copy_size = std::min(value.size(), N - 1);
if (copy_size > 0)
std::memcpy(field.data(), value.data(), copy_size);
AppendBytes(packet, field.data(), field.size());
}
}
std::vector<uint8_t> BuildQuestInfoPacket(const QuestInfoPacketData& data)
{
packet_quest_info header {};
header.header = GC::QUEST_INFO;
header.length = sizeof(header);
header.index = data.quest_index;
header.flag = data.send_flags;
std::vector<uint8_t> packet;
packet.reserve(sizeof(header) + 128);
AppendBytes(packet, &header, sizeof(header));
if (data.send_flags & QUEST_SEND_ISBEGIN_LOCAL)
{
const uint8_t is_begin = data.is_begin ? 1 : 0;
AppendBytes(packet, &is_begin, sizeof(is_begin));
}
if (data.send_flags & QUEST_SEND_TITLE_LOCAL)
AppendFixedString<30 + 1>(packet, data.title);
if (data.send_flags & QUEST_SEND_CLOCK_NAME_LOCAL)
AppendFixedString<16 + 1>(packet, data.clock_name);
if (data.send_flags & QUEST_SEND_CLOCK_VALUE_LOCAL)
AppendBytes(packet, &data.clock_value, sizeof(data.clock_value));
if (data.send_flags & QUEST_SEND_COUNTER_NAME_LOCAL)
AppendFixedString<16 + 1>(packet, data.counter_name);
if (data.send_flags & QUEST_SEND_COUNTER_VALUE_LOCAL)
AppendBytes(packet, &data.counter_value, sizeof(data.counter_value));
if (data.send_flags & QUEST_SEND_ICON_FILE_LOCAL)
AppendFixedString<24 + 1>(packet, data.icon_file);
auto* final_header = reinterpret_cast<packet_quest_info*>(packet.data());
final_header->length = static_cast<uint16_t>(packet.size());
return packet;
}
}

23
src/game/quest_packet.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace quest
{
struct QuestInfoPacketData
{
uint16_t quest_index = 0;
uint8_t send_flags = 0;
bool is_begin = false;
std::string title;
std::string clock_name;
int clock_value = 0;
std::string counter_name;
int counter_value = 0;
std::string icon_file;
};
std::vector<uint8_t> BuildQuestInfoPacket(const QuestInfoPacketData& data);
}

View File

@@ -1,8 +1,8 @@
#include "stdafx.h"
#include "constants.h"
#include "quest_packet.h"
#include "questmanager.h"
#include "packet_structs.h"
#include "buffer_manager.h"
#include "char.h"
#include "desc_client.h"
#include "questevent.h"
@@ -234,75 +234,34 @@ namespace quest
assert(m_iSendToClient);
assert(m_RunningQuestState);
packet_quest_info qi;
TEMP_BUFFER payload;
qi.header = GC::QUEST_INFO;
qi.length = sizeof(struct packet_quest_info);
qi.index = m_RunningQuestState->iIndex;
qi.flag = m_iSendToClient;
QuestInfoPacketData packet_data {};
packet_data.quest_index = static_cast<uint16_t>(m_RunningQuestState->iIndex);
packet_data.send_flags = static_cast<uint8_t>(m_iSendToClient);
packet_data.is_begin = m_RunningQuestState->bStart;
packet_data.title = m_RunningQuestState->_title;
packet_data.clock_name = m_RunningQuestState->_clock_name;
packet_data.clock_value = m_RunningQuestState->_clock_value;
packet_data.counter_name = m_RunningQuestState->_counter_name;
packet_data.counter_value = m_RunningQuestState->_counter_value;
packet_data.icon_file = m_RunningQuestState->_icon_file;
if (m_iSendToClient & QUEST_SEND_ISBEGIN)
{
BYTE temp = m_RunningQuestState->bStart?1:0;
payload.write(&temp,1);
qi.length+=1;
sys_log(1, "QUEST BeginFlag %d", (int)temp);
}
sys_log(1, "QUEST BeginFlag %d", static_cast<int>(m_RunningQuestState->bStart ? 1 : 0));
if (m_iSendToClient & QUEST_SEND_TITLE)
{
m_RunningQuestState->_title.reserve(30+1);
payload.write(m_RunningQuestState->_title.c_str(), 30+1);
qi.length+=30+1;
sys_log(1, "QUEST Title %s", m_RunningQuestState->_title.c_str());
}
if (m_iSendToClient & QUEST_SEND_CLOCK_NAME)
{
m_RunningQuestState->_clock_name.reserve(16+1);
payload.write(m_RunningQuestState->_clock_name.c_str(), 16+1);
qi.length+=16+1;
sys_log(1, "QUEST Clock Name %s", m_RunningQuestState->_clock_name.c_str());
}
if (m_iSendToClient & QUEST_SEND_CLOCK_VALUE)
{
payload.write(&m_RunningQuestState->_clock_value, sizeof(int));
qi.length+=4;
sys_log(1, "QUEST Clock Value %d", m_RunningQuestState->_clock_value);
}
if (m_iSendToClient & QUEST_SEND_COUNTER_NAME)
{
m_RunningQuestState->_counter_name.reserve(16+1);
payload.write(m_RunningQuestState->_counter_name.c_str(), 16+1);
qi.length+=16+1;
sys_log(1, "QUEST Counter Name %s", m_RunningQuestState->_counter_name.c_str());
}
if (m_iSendToClient & QUEST_SEND_COUNTER_VALUE)
{
payload.write(&m_RunningQuestState->_counter_value, sizeof(int));
qi.length+=4;
sys_log(1, "QUEST Counter Value %d", m_RunningQuestState->_counter_value);
}
if (m_iSendToClient & QUEST_SEND_ICON_FILE)
{
m_RunningQuestState->_icon_file.reserve(24+1);
payload.write(m_RunningQuestState->_icon_file.c_str(), 24+1);
qi.length+=24+1;
sys_log(1, "QUEST Icon File %s", m_RunningQuestState->_icon_file.c_str());
}
TEMP_BUFFER buf;
buf.write(&qi, sizeof(qi));
if (payload.size() > 0)
buf.write(payload.read_peek(), payload.size());
CQuestManager::instance().GetCurrentCharacterPtr()->GetDesc()->Packet(buf.read_peek(),buf.size());
auto packet = BuildQuestInfoPacket(packet_data);
CQuestManager::instance().GetCurrentCharacterPtr()->GetDesc()->Packet(packet.data(), packet.size());
m_iSendToClient = 0;

View File

@@ -0,0 +1,6 @@
#pragma once
inline bool HasRecentRequestCooldown(int last_request_pulse, int current_pulse, int cooldown_pulses)
{
return last_request_pulse > 0 && current_pulse - last_request_pulse < cooldown_pulses;
}