tests: cover quest framing and restart cooldowns
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
80
src/game/quest_packet.cpp
Normal 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
23
src/game/quest_packet.h
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
6
src/game/request_cooldown.h
Normal file
6
src/game/request_cooldown.h
Normal 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;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ endif()
|
||||
add_executable(metin_smoke_tests
|
||||
smoke_auth.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/SecureCipher.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/quest_packet.cpp
|
||||
)
|
||||
|
||||
add_executable(metin_login_smoke
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "common/packet_headers.h"
|
||||
#include "game/stdafx.h"
|
||||
#include "common/packet_headers.h"
|
||||
#include "common/tables.h"
|
||||
#include "game/packet_structs.h"
|
||||
#include "game/quest_packet.h"
|
||||
#include "game/request_cooldown.h"
|
||||
#include "game/SecureCipher.h"
|
||||
#include "libthecore/fdwatch.h"
|
||||
#include "libthecore/signal.h"
|
||||
@@ -366,6 +371,55 @@ void TestFdwatchSlotReuseAfterDelete()
|
||||
close(sockets_b[0]);
|
||||
close(sockets_b[1]);
|
||||
}
|
||||
|
||||
void TestQuestInfoPacketFraming()
|
||||
{
|
||||
quest::QuestInfoPacketData data {};
|
||||
data.quest_index = 77;
|
||||
data.send_flags = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
|
||||
data.is_begin = true;
|
||||
data.title = "Mall reward";
|
||||
data.clock_name = "Soon";
|
||||
data.clock_value = 15;
|
||||
data.counter_name = "Kills";
|
||||
data.counter_value = 2;
|
||||
data.icon_file = "d:/icon/test.tga";
|
||||
|
||||
const auto quest_packet = quest::BuildQuestInfoPacket(data);
|
||||
Expect(!quest_packet.empty(), "Quest info packet is empty");
|
||||
Expect(quest_packet.size() == sizeof(packet_quest_info) + 1 + 31 + 17 + 4 + 17 + 4 + 25,
|
||||
"Unexpected quest info packet size");
|
||||
|
||||
const auto* quest_header = reinterpret_cast<const packet_quest_info*>(quest_packet.data());
|
||||
Expect(quest_header->header == GC::QUEST_INFO, "Unexpected quest info header");
|
||||
Expect(quest_header->length == quest_packet.size(), "Quest info packet length does not match payload size");
|
||||
Expect(quest_packet[sizeof(packet_quest_info)] == 1, "Quest begin flag payload mismatch");
|
||||
|
||||
TPacketGCItemGet item_get {};
|
||||
item_get.header = GC::ITEM_GET;
|
||||
item_get.length = sizeof(item_get);
|
||||
item_get.dwItemVnum = 50187;
|
||||
item_get.bCount = 1;
|
||||
item_get.bArg = 0;
|
||||
|
||||
std::vector<uint8_t> stream = quest_packet;
|
||||
const auto* item_bytes = reinterpret_cast<const uint8_t*>(&item_get);
|
||||
stream.insert(stream.end(), item_bytes, item_bytes + sizeof(item_get));
|
||||
|
||||
const size_t next_frame_offset = quest_header->length;
|
||||
Expect(stream.size() >= next_frame_offset + sizeof(item_get), "Combined stream truncated after quest packet");
|
||||
|
||||
const auto* next_frame = reinterpret_cast<const TPacketGCItemGet*>(stream.data() + next_frame_offset);
|
||||
Expect(next_frame->header == GC::ITEM_GET, "Quest info packet left trailing bytes before next frame");
|
||||
Expect(next_frame->length == sizeof(TPacketGCItemGet), "Item get packet length mismatch after quest packet");
|
||||
}
|
||||
|
||||
void TestRequestCooldownGuard()
|
||||
{
|
||||
Expect(!HasRecentRequestCooldown(0, 5, 10), "Initial zero request pulse should not trigger cooldown");
|
||||
Expect(HasRecentRequestCooldown(95, 100, 10), "Recent request pulse should still be on cooldown");
|
||||
Expect(!HasRecentRequestCooldown(90, 100, 10), "Cooldown boundary should allow request");
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -379,6 +433,8 @@ int main()
|
||||
TestCheckpointBackendMetadata();
|
||||
TestFdwatchReadAndOneshotWrite();
|
||||
TestFdwatchSlotReuseAfterDelete();
|
||||
TestQuestInfoPacketFraming();
|
||||
TestRequestCooldownGuard();
|
||||
std::cout << "metin smoke tests passed\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user